feat: integrate durable authorization audit and runtime composition
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
tegwick 2026-09-28 12:01:42 +02:00
parent 3e386147fd
commit c9b6916dac
14 changed files with 767 additions and 16 deletions

View file

@ -3,6 +3,8 @@ from __future__ import annotations
import asyncio
from contextlib import asynccontextmanager, suppress
import httpx
from fastapi import FastAPI, Response, status
from hub_core import __version__
@ -27,7 +29,8 @@ from hub_core.runtime.workload_projection import (
WorkloadProjectionService,
)
from hub_core.runtime.workload_projection_routes import create_workload_projection_router
from hub_core.security.boundary import AccessBoundary, AccessController
from hub_core.security.boundary import AccessBoundary, AccessController, FactSource
from hub_core.security.config import SecuritySettings
def create_app(
@ -37,8 +40,25 @@ def create_app(
repo_projection_client: RepoProjectionClient | None = None,
workload_projection_client: WorkloadProjectionClient | None = None,
access_controller: AccessController | None = None,
access_facts: FactSource | None = None,
security_settings: SecuritySettings | None = None,
) -> FastAPI:
resolved_settings = settings or RuntimeSettings.from_env()
# Importing this module also constructs the standalone app. Environment
# configuration is activated only by an explicit owner-facts composition;
# without that adapter the default app stays closed, not import-broken.
if security_settings is None and access_facts is not None:
security_settings = SecuritySettings.from_env()
security_client = None
if access_controller is not None and (access_facts is not None or security_settings is not None):
raise ValueError("choose an access controller or owner-facts composition")
if security_settings is not None or access_facts is not None:
if not resolved_settings.enforce_access:
raise ValueError("security composition requires enforcement mode")
if security_settings is None or access_facts is None:
raise ValueError("security composition requires configuration and authoritative owner facts")
security_client = httpx.AsyncClient(trust_env=False)
access_controller = security_settings.compose(facts=access_facts, client=security_client)
resolved_store = port_store or _create_store(resolved_settings)
owns_store = port_store is None
resolved_repo_projection_client = repo_projection_client
@ -84,15 +104,19 @@ def create_app(
await workload_projection.refresh()
except WorkloadProjectionRejected:
pass
yield
if refresh_task is not None:
refresh_task.cancel()
with suppress(asyncio.CancelledError):
await refresh_task
if owns_repo_projection_client:
await resolved_repo_projection_client.aclose() # type: ignore[union-attr]
if owns_store and (closer := getattr(resolved_store, "aclose", None)):
await closer()
try:
yield
finally:
if refresh_task is not None:
refresh_task.cancel()
with suppress(asyncio.CancelledError):
await refresh_task
if security_client is not None:
await security_client.aclose()
if owns_repo_projection_client:
await resolved_repo_projection_client.aclose() # type: ignore[union-attr]
if owns_store and (closer := getattr(resolved_store, "aclose", None)):
await closer()
app = FastAPI(
title="Hub Core Runtime",