cognee_http_server/state.rs
1//! Application state — a single `Clone`-able struct injected into every handler
2//! via `axum::extract::State`.
3//!
4//! All fields are `Arc<…>` so `AppState::clone()` is cheap. Axum clones the
5//! state once per request.
6
7use std::sync::Arc;
8
9#[cfg(feature = "telemetry")]
10use cognee_observability::TelemetryGuard;
11
12use cognee_core::PipelineRunRegistry;
13use cognee_core::pipeline_run_registry::DefaultPipelineRunRegistry;
14use cognee_database::{
15 DatabaseConnection, NoopPipelineRunRepository, PipelineRunRepository,
16 SeaOrmPipelineRunRepository,
17};
18
19use crate::{
20 auth_resolver::AuthResolver,
21 components::ComponentHandles,
22 config::{HttpServerConfig, RegistryConfig},
23 error::ServerError,
24 observability::{BufferConfig, SpanBuffer},
25 sync::SyncRegistry,
26};
27
28// ─── AppState ────────────────────────────────────────────────────────────────
29
30/// Per-server dependency container shared across all handlers.
31///
32/// Fields that depend on closed-side features (auth chain, mailer, etc.)
33/// are kept as injection seams (`Option<Arc<dyn ...>>`) so the closed
34/// `cognee-http-cloud` crate can populate them via the `RouterBuilder`.
35#[derive(Clone)]
36pub struct AppState {
37 /// HTTP server config (host, port, CORS, JWT, …).
38 pub config: Arc<HttpServerConfig>,
39
40 /// Background pipeline-run lifecycle registry.
41 ///
42 /// The inner `Arc<dyn PipelineRunRegistry>` is `Clone`-able cheaply.
43 pub pipelines: Arc<dyn PipelineRunRegistry>,
44
45 /// Pre-built component handles (database, storage, delete_service,
46 /// ontology_manager). `None` until `AppState::build` fully initialises
47 /// the backends — most tests leave this `None` and stub out the
48 /// relevant functionality directly.
49 pub lib: Option<Arc<ComponentHandles>>,
50
51 /// Closed-side authentication chain. `None` in pure-OSS builds; closed
52 /// embedders install one via `RouterBuilder::with_auth_resolver(...)`
53 /// or `RouterBuilder::with_extra_validator(...)`. When `None`, the
54 /// `AuthenticatedUser` extractor falls through to either a synthetic
55 /// default user (`require_authentication=false`) or a 401
56 /// (`require_authentication=true`).
57 pub auth_resolver: Option<Arc<dyn AuthResolver>>,
58
59 /// Health checker for /health endpoints. `None` falls back to a
60 /// synthetic `MockHealthChecker`. Embedders populate by calling
61 /// [`AppState::install_real_health_checker`] after wiring `lib`.
62 pub health: Option<Arc<dyn crate::routers::health::HealthChecker>>,
63
64 /// In-memory span buffer feeding `GET /api/v1/activity/spans`.
65 /// Always populated — `BufferConfig::from_env()` reads the cap. To
66 /// effectively disable the buffer pass `BufferConfig { max_traces: 0, .. }`.
67 pub spans: Arc<SpanBuffer>,
68
69 /// In-memory registry tracking one running cloud sync per user. Always
70 /// populated; the registry itself starts empty.
71 pub sync: Arc<SyncRegistry>,
72
73 /// Flush-on-drop guard for the OpenTelemetry exporter (decision 9).
74 /// Held only for its `Drop` side effect: the last `Arc` released calls
75 /// `provider.force_flush()` + `provider.shutdown()`. `None` when built
76 /// without explicit telemetry init (test paths, library embedders).
77 #[cfg(feature = "telemetry")]
78 pub telemetry_guard: Option<Arc<TelemetryGuard>>,
79}
80
81impl AppState {
82 /// Build a no-op `Arc<dyn PipelineRunRegistry>` backed by a
83 /// `NoopPipelineRunRepository`. Useful in tests that construct `AppState`
84 /// directly without a real database.
85 pub fn noop_pipelines() -> Arc<dyn PipelineRunRegistry> {
86 let repo = NoopPipelineRunRepository::arc();
87 let cfg = RegistryConfig::default();
88 DefaultPipelineRunRegistry::new(repo, cfg)
89 }
90
91 /// Construct an `AppState` with the given config; all optional components
92 /// default to `None`. Later phases call this and then set individual fields.
93 ///
94 /// Builds `DefaultPipelineRunRegistry` from the config's registry knobs and
95 /// runs the startup orphan-reset per pipelines.md §12 — any `INITIATED` /
96 /// `STARTED` rows left over from a previous unclean shutdown are rewritten to
97 /// `ERRORED` with `reason = "server_restart_orphan"`.
98 pub async fn build(config: HttpServerConfig) -> Result<Self, ServerError> {
99 // Build an in-memory-only pipeline run repository backed by a temporary
100 // SQLite database. The real repository (backed by the server's own DB)
101 // is wired when `lib` is populated. For now we use the shared
102 // `cognee_database::NoopPipelineRunRepository` (gap 08-07) so the
103 // registry is always non-None.
104 let repo = NoopPipelineRunRepository::arc();
105 let registry_cfg = config.to_registry_config();
106 let pipelines: Arc<dyn PipelineRunRegistry> =
107 DefaultPipelineRunRegistry::new(repo, registry_cfg);
108
109 Ok(Self {
110 config: Arc::new(config),
111 pipelines,
112 lib: None,
113 auth_resolver: None,
114 health: None,
115 spans: Arc::new(SpanBuffer::new(BufferConfig::from_env())),
116 sync: Arc::new(SyncRegistry::new()),
117 #[cfg(feature = "telemetry")]
118 telemetry_guard: None,
119 })
120 }
121
122 /// Convenience accessor for the component handles.
123 ///
124 /// Returns `None` when the server is running in test mode without backends
125 /// wired. Most integration tests build their own `ComponentHandles` directly.
126 pub fn components(&self) -> Option<&ComponentHandles> {
127 self.lib.as_deref()
128 }
129
130 /// Replace the `health` field with a `RealHealthChecker` built from the
131 /// currently-wired `ComponentHandles`. No-op when `lib` is `None`.
132 pub fn install_real_health_checker(&mut self) {
133 if let Some(handles) = &self.lib {
134 let checker = crate::health::RealHealthChecker::new(Arc::clone(handles), &self.config);
135 self.health = Some(Arc::new(checker));
136 }
137 }
138}
139
140// ─── Build state with a real database ─────────────────────────────────────────
141
142impl AppState {
143 /// Build `AppState` with a real `DatabaseConnection` wired into the pipeline
144 /// registry. Used by the server startup path when backend env vars are
145 /// present.
146 ///
147 /// Runs the orphan-reset once on startup per pipelines.md §12.
148 pub async fn build_with_db(
149 config: HttpServerConfig,
150 db: Arc<DatabaseConnection>,
151 ) -> Result<Self, ServerError> {
152 let repo = Arc::new(SeaOrmPipelineRunRepository::new(Arc::clone(&db)))
153 as Arc<dyn PipelineRunRepository>;
154 let registry_cfg = config.to_registry_config();
155
156 // Run orphan reset on startup (best-effort — non-fatal).
157 let pipelines: Arc<dyn PipelineRunRegistry> =
158 match DefaultPipelineRunRegistry::new_with_orphan_reset(repo, registry_cfg).await {
159 Ok(r) => r,
160 Err(e) => {
161 tracing::warn!(
162 "pipeline registry startup orphan-reset failed (non-fatal): {e}"
163 );
164 // Fall back to plain new() without reset.
165 let repo2 = Arc::new(SeaOrmPipelineRunRepository::new(Arc::clone(&db)))
166 as Arc<dyn PipelineRunRepository>;
167 DefaultPipelineRunRegistry::new(repo2, config.to_registry_config())
168 }
169 };
170
171 Ok(Self {
172 config: Arc::new(config),
173 pipelines,
174 lib: None,
175 auth_resolver: None,
176 health: None,
177 spans: Arc::new(SpanBuffer::new(BufferConfig::from_env())),
178 sync: Arc::new(SyncRegistry::new()),
179 #[cfg(feature = "telemetry")]
180 telemetry_guard: None,
181 })
182 }
183}