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>
65 lines
2.4 KiB
Python
65 lines
2.4 KiB
Python
"""Registrar factory — selects the appropriate AgentRegistrar based on settings."""
|
|
|
|
import logging
|
|
|
|
from .base import AgentRegistrar
|
|
from .stub import StubRegistrar
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def create_registrar(config) -> AgentRegistrar:
|
|
"""Create the appropriate registrar based on agent_registrar setting."""
|
|
driver = config.agent_registrar
|
|
|
|
if driver == "stub":
|
|
return StubRegistrar()
|
|
|
|
if driver == "keycloak":
|
|
if not config.keycloak_admin_client_secret:
|
|
logger.warning("Keycloak secret not configured, using stub")
|
|
return StubRegistrar()
|
|
from .keycloak import KeycloakRegistrar
|
|
return KeycloakRegistrar(
|
|
base_url=config.keycloak_url,
|
|
realm=config.keycloak_realm,
|
|
client_id=config.keycloak_admin_client_id,
|
|
client_secret=config.keycloak_admin_client_secret,
|
|
)
|
|
|
|
if driver == "entra":
|
|
if not config.entra_client_secret:
|
|
logger.warning("Entra secret not configured, using stub")
|
|
return StubRegistrar()
|
|
from .entra import EntraRegistrar
|
|
return EntraRegistrar(
|
|
tenant_id=config.entra_tenant_id,
|
|
client_id=config.entra_client_id,
|
|
client_secret=config.entra_client_secret,
|
|
agent_blueprint_id=config.entra_agent_blueprint_id,
|
|
)
|
|
|
|
if driver == "auto":
|
|
if config.entra_client_secret:
|
|
from .entra import EntraRegistrar
|
|
logger.info("Auto-selected Entra registrar")
|
|
return EntraRegistrar(
|
|
tenant_id=config.entra_tenant_id,
|
|
client_id=config.entra_client_id,
|
|
client_secret=config.entra_client_secret,
|
|
agent_blueprint_id=config.entra_agent_blueprint_id,
|
|
)
|
|
if config.keycloak_admin_client_secret:
|
|
from .keycloak import KeycloakRegistrar
|
|
logger.info("Auto-selected Keycloak registrar")
|
|
return KeycloakRegistrar(
|
|
base_url=config.keycloak_url,
|
|
realm=config.keycloak_realm,
|
|
client_id=config.keycloak_admin_client_id,
|
|
client_secret=config.keycloak_admin_client_secret,
|
|
)
|
|
logger.warning("No IdP configured, using stub registrar")
|
|
return StubRegistrar()
|
|
|
|
logger.warning("Unknown registrar driver: %s, using stub", driver)
|
|
return StubRegistrar()
|