Skip to main content

assay_engine/
state.rs

1//! Composed engine state.
2//!
3//! `EngineState<S>` bundles the per-module contexts. Phase 3 composed
4//! workflow + dashboard. Phase 8 adds an optional `AuthCtx` field
5//! (gated on the `auth` Cargo feature) and an `axum::FromRef` impl so
6//! the auth router's handlers can extract `AuthCtx` directly via
7//! `State<AuthCtx>`. The `AdminApiKeys` value flows through the same
8//! seam so admin endpoints (`/admin/oidc/*`, `/admin/auth/*`) can
9//! enforce the configured bearer-token allowlist.
10
11use std::sync::Arc;
12
13use assay_auth::AuthCtx;
14use assay_dashboard::DashboardCtx;
15#[cfg(feature = "vault")]
16use assay_vault::VaultCtx;
17use assay_workflow::{WorkflowCtx, WorkflowStore};
18
19pub use assay_auth::state::AdminApiKeys;
20
21use crate::config::EngineConfig;
22
23#[derive(Clone)]
24pub struct EngineState<S: WorkflowStore> {
25    pub workflow: Arc<WorkflowCtx<S>>,
26    pub dashboard: Arc<DashboardCtx>,
27    /// Composed auth context — present iff the runtime
28    /// `engine.modules.auth.enabled` row is TRUE. `axum::FromRef`
29    /// extracts it for the auth router's handlers.
30    pub auth: Option<AuthCtx>,
31    /// Composed vault context — present iff the `vault` Cargo feature
32    /// is on AND the runtime `engine.modules.vault.enabled` row is
33    /// TRUE. Plan 17's v0.3.0 module — KV / transit / collections /
34    /// share / sealing / dynamic creds / BW-compat all hang off this.
35    #[cfg(feature = "vault")]
36    pub vault: Option<VaultCtx>,
37    /// Admin API keys for the `/admin/*` HTTP surface — checked by
38    /// auth handlers via `axum::extract::FromRef<EngineState<S>>` so
39    /// the same value flows from `engine.toml` through to per-request
40    /// auth gating without a global static. Empty when no admin
41    /// surface is configured.
42    pub admin_api_keys: Arc<Vec<String>>,
43    /// Names of modules attached/loaded during boot — surfaced through
44    /// `/healthz` for ops visibility (which functional modules this
45    /// engine instance has wired up).
46    pub modules: Arc<Vec<String>>,
47    /// This instance's row in `engine.instances`. Lets `/healthz` and
48    /// future visibility endpoints identify which engine process is
49    /// answering.
50    pub instance_id: uuid::Uuid,
51    /// `assay-engine` crate version. Returned in
52    /// `/api/v1/engine/core/health` so external monitors can correlate
53    /// health checks with deployments.
54    pub engine_version: &'static str,
55    /// Wall-clock seconds since the UNIX epoch when this engine process
56    /// finished booting. Surfaced through `/api/v1/engine/core/info` so the
57    /// engine console can display "uptime" without an extra DB lookup
58    /// per request — matches the value the engine wrote to its
59    /// `engine.instances` row at boot.
60    pub started_at: f64,
61    /// Parsed `engine.toml` snapshot — admin endpoints serialise this
62    /// (with secrets redacted) so the engine console's "Config" pane
63    /// shows the operator exactly what the running engine is using.
64    /// `Arc` so cloning state per-request stays cheap.
65    pub engine_config: Arc<EngineConfig>,
66}
67
68impl<S: WorkflowStore> axum::extract::FromRef<EngineState<S>> for AuthCtx {
69    /// FromRef impl so auth handlers can extract the resolved AuthCtx
70    /// via `State<AuthCtx>`. Panics when the engine binary mounted the
71    /// auth router without composing an AuthCtx — a misconfiguration
72    /// the engine boot path is responsible for preventing (the auth
73    /// router is only mounted when `state.auth.is_some()`).
74    fn from_ref(s: &EngineState<S>) -> Self {
75        s.auth
76            .clone()
77            .expect("auth router mounted without an AuthCtx — engine boot bug")
78    }
79}
80
81impl<S: WorkflowStore> axum::extract::FromRef<EngineState<S>> for AdminApiKeys {
82    fn from_ref(s: &EngineState<S>) -> Self {
83        AdminApiKeys(Arc::clone(&s.admin_api_keys))
84    }
85}
86
87#[cfg(feature = "vault")]
88impl<S: WorkflowStore> axum::extract::FromRef<EngineState<S>> for VaultCtx {
89    /// FromRef impl so vault handlers (added Phase 1+) can extract the
90    /// resolved VaultCtx via `State<VaultCtx>`. Panics when the vault
91    /// router is mounted without composing a VaultCtx — same engine-
92    /// boot-bug surface AuthCtx uses.
93    fn from_ref(s: &EngineState<S>) -> Self {
94        s.vault
95            .clone()
96            .expect("vault router mounted without a VaultCtx — engine boot bug")
97    }
98}