"""Health and readiness endpoints.""" from __future__ import annotations from fastapi import APIRouter, Depends from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from app.core.config import settings from app.core.database import get_session from app.infrastructure.adapters.registry import get_model_provider router = APIRouter(tags=["health"]) @router.get("/health") async def health() -> dict: """Liveness. Must stay dependency-free so it answers during a DB outage.""" return {"status": "ok", "app": settings.app_name, "env": settings.app_env} @router.get("/health/ready") async def readiness(session: AsyncSession = Depends(get_session)) -> dict: """Readiness: verifies the database and reports which adapters are active.""" checks: dict[str, object] = {} try: await session.execute(text("SELECT 1")) checks["database"] = "ok" except Exception as exc: # noqa: BLE001 - reported, not raised checks["database"] = f"error: {exc.__class__.__name__}" try: model_health = await get_model_provider().health() checks["model_provider"] = "ok" if model_health.ok else f"degraded: {model_health.detail}" except Exception as exc: # noqa: BLE001 checks["model_provider"] = f"error: {exc.__class__.__name__}" checks["adapters"] = { "ai": settings.ai_default_provider, "git": settings.git_provider, "domain_router": settings.domain_router, "sandbox": settings.sandbox_provider, "payment": settings.payment_provider, } # Live-feed visibility: how many browser sockets this process is serving, # and whether the cross-process relay (Redis) is connected. An operator # wondering "is realtime working?" should not have to read the logs. try: from app.interfaces.realtime.bus import get_bus from app.interfaces.realtime.router import realtime_manager manager = realtime_manager() checks["realtime"] = { "connections": manager.connection_count, "channels": manager.channel_count, "redis_relay": "connected" if get_bus().redis_healthy else "in_process_only", } except Exception: # noqa: BLE001 - health must answer regardless checks["realtime"] = "unavailable" healthy = checks.get("database") == "ok" return {"status": "ok" if healthy else "degraded", "checks": checks}