Merges the standalone llm-principal-broker (1,132 LOC) into fastapi-gsap
as an in-process module. The previous architecture had two FastAPI
processes where the broker called GSAP over HTTP on every delegation
creation; now the lifecycle code uses GSAP's own async DB engine
directly and inserts AuthorizationContextDB rows in the same
transaction context.
New module: gsap_broker/delegations/
models.py Pydantic request/response shapes
storage.py DelegationDB SQLModel sharing the GSAP engine
lifecycle.py DelegationManager — in-process AC issuance via
AuthorizationContextDB.insert (no HTTP self-call)
cleanup.py 30s background task for stale delegations
router.py /delegations/* FastAPI router (4 endpoints)
registrars/
base.py AgentRegistrar Protocol + AgentCredentials
stub.py dev-mode no-op
keycloak.py Keycloak Admin REST API
entra.py Microsoft Entra Agent ID via Graph (lazy import)
factory.py driver selection (auto/stub/keycloak/entra)
Wiring:
app.py mounts the delegations router and starts the cleanup task in
the existing lifespan context manager.
settings.py absorbs the keycloak_admin_*, entra_*, and
agent_registrar fields from the old broker's settings.
pyproject.toml adds an optional `entra` extra for the msal dep.
Behaviour preservation:
- Endpoints kept identical: POST /, POST /{id}/revoke, GET /{id}, GET /
- Chronicle event codes preserved: 0x3001 / 0x3003 / 0x3004
- DelegationScope defaults unchanged (max_ttl_minutes=60, max_commands=500)
- Capability ceiling -> capability_mask conversion documented inline
Smoke test: `python -c "from gsap_broker.app import app"` loads cleanly
with 26 routes including the four /delegations/ endpoints.
The standalone llm-principal-broker repo is archived to
~/projects/archive/llm-principal-broker.
Signed-off-by: Tyler King <tking@guildhouse.dev>
56 lines
2.5 KiB
Python
56 lines
2.5 KiB
Python
"""fastapi-gsap: Lightweight GSAP broker — GCAP-SPEC-SHELLBOUND-BROKER-0001.
|
|
|
|
Also hosts the delegation lifecycle module
|
|
(GCAP-SPEC-LLM-PRINCIPAL-BROKER-0001) absorbed from the standalone
|
|
llm-principal-broker service. The delegation router is mounted at
|
|
``/delegations`` and the storage layer reuses GSAP's existing async DB
|
|
engine — no separate process, no HTTP self-call.
|
|
"""
|
|
import asyncio
|
|
import structlog
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from gsap_broker.settings import settings
|
|
from gsap_broker.db import init_db
|
|
from gsap_broker.routers import authorize, complete, session, elevate, health, drivers, connectors, functions
|
|
from gsap_broker import mcp
|
|
from gsap_broker.delegations.router import router as delegations_router, manager as delegation_manager
|
|
from gsap_broker.delegations.cleanup import cleanup_loop as delegation_cleanup_loop
|
|
# Import storage so SQLModel.metadata picks up the delegations table at init_db().
|
|
from gsap_broker.delegations import storage as _delegations_storage # noqa: F401
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
await init_db()
|
|
cleanup_task = asyncio.create_task(delegation_cleanup_loop(delegation_manager))
|
|
logger.info(
|
|
"fastapi-gsap started",
|
|
broker_did=settings.broker_did,
|
|
delegations_router="/delegations",
|
|
)
|
|
try:
|
|
yield
|
|
finally:
|
|
cleanup_task.cancel()
|
|
try:
|
|
await cleanup_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
app = FastAPI(title="fastapi-gsap", description="GSAP broker PoC — GCAP-SPEC-SHELLBOUND-BROKER-0001",
|
|
version="0.1.0", lifespan=lifespan)
|
|
app.add_middleware(CORSMiddleware, allow_origins=settings.cors_origins, allow_credentials=True,
|
|
allow_methods=["*"], allow_headers=["*"])
|
|
app.include_router(authorize.router, prefix="/governance", tags=["AC"])
|
|
app.include_router(complete.router, prefix="/governance", tags=["CR"])
|
|
app.include_router(session.router, prefix="/governance", tags=["Session"])
|
|
app.include_router(elevate.router, prefix="/governance", tags=["Elevation"])
|
|
app.include_router(drivers.router, prefix="/governance", tags=["Drivers"])
|
|
app.include_router(connectors.router, prefix="/connectors", tags=["Connectors"])
|
|
app.include_router(functions.router, prefix="/functions", tags=["Functions"])
|
|
app.include_router(health.router, tags=["Health"])
|
|
app.include_router(mcp.router, tags=["MCP"])
|
|
app.include_router(delegations_router)
|