//! Handler for health checks. use axum::{extract::State, Json}; use tracing::instrument; use crate::{dto::HealthResponse, error::Result, state::AppState}; use stemedb_storage::{key_codec, CircuitBreakerStore, KVStore, QuarantineStore}; /// Health check endpoint. /// /// Returns service status ("healthy"), API version, and the total number of assertions /// currently stored in the database. Useful for monitoring and load balancer health checks. /// /// Also updates Prometheus gauges for assertions_total, quarantine_pending, /// and circuit_breakers_open on each call. #[utoipa::path( get, path = "/v1/health", responses( (status = 200, description = "Service is healthy", body = HealthResponse), ), tag = "health" )] #[instrument(skip(state))] pub async fn health_check(State(state): State) -> Result> { // Count assertions in the database let assertions_count = count_assertions(&state).await?; // Update Prometheus gauges (best-effort — don't fail health check) metrics::gauge!("stemedb_assertions_total").set(assertions_count as f64); let pending_count = state.quarantine_store.list_pending(usize::MAX).await.map(|v| v.len()).unwrap_or_default(); metrics::gauge!("stemedb_quarantine_pending").set(pending_count as f64); let tripped_count = state .circuit_breaker_store .list_tripped(usize::MAX) .await .map(|v| v.len()) .unwrap_or_default(); metrics::gauge!("stemedb_circuit_breakers_open").set(tripped_count as f64); Ok(Json(HealthResponse { status: "healthy".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), assertions_count, })) } /// Count the number of assertions in the database. async fn count_assertions(state: &AppState) -> Result { // Read the atomic assertion count maintained by the ingestion pipeline let count_key = key_codec::assertion_count_key(); match state.store.get(&count_key).await? { Some(bytes) if bytes.len() == 8 => { Ok(u64::from_le_bytes(bytes.try_into().unwrap_or([0u8; 8]))) } _ => Ok(0), } }