Files
DECNET/decnet/web/swarm_api.py
anti d1b7e94325 fix(swarm): inject peer cert into ASGI scope for uvicorn <= 0.44
Uvicorn's h11/httptools HTTP protocols don't populate scope['extensions']['tls'], so /swarm/heartbeat's per-request cert pinning was 403ing every call despite CERT_REQUIRED validating the cert at handshake. Patch RequestResponseCycle.__init__ on both protocol modules to read the peer cert off the asyncio transport and write DER bytes into scope['extensions']['tls']['client_cert_chain']. Importing the module from swarm_api.py auto-installs the patch in the swarmctl uvicorn worker before any request is served.
2026-04-19 22:09:11 -04:00

68 lines
2.1 KiB
Python

"""DECNET SWARM Controller — master-side control plane.
Runs as an independent FastAPI/uvicorn process. Isolated from
``decnet.web.api`` so controller failure cannot cascade to the main API,
ingester, or dashboard (mirrors the existing pattern used by
``decnet api`` with ``start_new_session=True``).
Responsibilities:
* host enrollment (issues CA-signed worker bundles);
* dispatching DecnetConfig shards to worker agents over mTLS;
* active health probes of enrolled workers.
The controller *reuses* the same ``get_repo`` dependency as the main API,
so SwarmHost / DeckyShard state is visible to both processes via the
shared DB.
"""
from __future__ import annotations
from decnet.web import _uvicorn_tls_scope # noqa: F401 # patches uvicorn on import
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from decnet.logging import get_logger
from decnet.swarm import pki
from decnet.swarm.client import ensure_master_identity
from decnet.web.dependencies import repo
from decnet.web.router.swarm import swarm_router
log = get_logger("swarm_api")
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
log.info("swarm-controller starting up")
# Make sure the CA and master client cert exist before we accept any
# request — enrollment needs them and AgentClient needs them.
pki.ensure_ca()
ensure_master_identity()
await repo.initialize()
log.info("swarm-controller ready")
yield
log.info("swarm-controller shutdown")
app: FastAPI = FastAPI(
title="DECNET SWARM Controller",
version="0.1.0",
lifespan=lifespan,
default_response_class=ORJSONResponse,
# No interactive docs: the controller is an internal management plane,
# not a public surface. Enable explicitly in dev if needed.
docs_url=None,
redoc_url=None,
openapi_url=None,
)
app.include_router(swarm_router)
@app.get("/health")
async def root_health() -> dict[str, str]:
"""Top-level liveness probe (no DB I/O)."""
return {"status": "ok", "role": "swarm-controller"}