chore: merge master into feat/issue-646-policy-guardrail-visibility (base sync)
This commit is contained in:
@@ -47,6 +47,12 @@ from webui.runtime_health import load_runtime_snapshot, snapshot_to_dict as runt
|
||||
from webui.runtime_views import render_runtime_page
|
||||
from webui.policy_inventory import load_policy_inventory, snapshot_to_dict as policy_snapshot_to_dict
|
||||
from webui.policy_views import render_policy_page
|
||||
from webui.system_health import (
|
||||
API_PATH as SYSTEM_HEALTH_API_PATH,
|
||||
load_system_health,
|
||||
process_uptime,
|
||||
snapshot_to_dict as system_health_to_dict,
|
||||
)
|
||||
|
||||
_READ_ONLY_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
|
||||
_AUDIT_MUTATION_PATHS = frozenset({"/audit", "/api/audit"})
|
||||
@@ -81,16 +87,43 @@ async def home(_request: Request) -> HTMLResponse:
|
||||
|
||||
|
||||
async def health(_request: Request) -> JSONResponse:
|
||||
"""Liveness only — deliberately cheap, runs no dependency probe (#634).
|
||||
|
||||
Every MVP key is retained so existing pollers keep working; the additions
|
||||
are a pointer to the structured API and the in-memory process uptime.
|
||||
Readiness lives at that API because answering it costs real probes.
|
||||
"""
|
||||
bind_host = _request.app.state.webui_bind_host
|
||||
started_at, uptime_seconds = process_uptime()
|
||||
return JSONResponse({
|
||||
"status": "ok",
|
||||
"service": "mcp-control-plane-webui",
|
||||
"mode": "read-only-mvp",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"deployment": deployment_snapshot(bind_host=bind_host),
|
||||
"started_at": started_at,
|
||||
"uptime_seconds": uptime_seconds,
|
||||
"system_health_api": SYSTEM_HEALTH_API_PATH,
|
||||
})
|
||||
|
||||
|
||||
def _truthy_flag(value: str | None) -> bool:
|
||||
return (value or "").strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
async def api_system_health(request: Request) -> JSONResponse:
|
||||
"""Structured read-only system health (#634).
|
||||
|
||||
`?deep=1` opts into the expensive network probe. The response status code
|
||||
reflects readiness so automated checks can branch on it without parsing the
|
||||
body: 200 when ready, 503 when a required dependency failed or never ran.
|
||||
"""
|
||||
deep = _truthy_flag(request.query_params.get("deep"))
|
||||
snapshot = load_system_health(deep=deep)
|
||||
payload = system_health_to_dict(snapshot)
|
||||
return JSONResponse(payload, status_code=200 if snapshot.ready else 503)
|
||||
|
||||
|
||||
async def queue(_request: Request) -> HTMLResponse:
|
||||
snapshot = load_queue_snapshot()
|
||||
return HTMLResponse(render_page(title="Queue", body_html=render_queue_page(snapshot)))
|
||||
@@ -413,6 +446,7 @@ def create_app(*, bind_host: str | None = None) -> Starlette:
|
||||
routes=[
|
||||
Route("/", home, methods=["GET"]),
|
||||
Route("/health", health, methods=["GET"]),
|
||||
Route(SYSTEM_HEALTH_API_PATH, api_system_health, methods=["GET"]),
|
||||
Route("/queue", queue, methods=["GET"]),
|
||||
Route("/api/queue", api_queue, methods=["GET"]),
|
||||
Route("/projects", projects, methods=["GET"]),
|
||||
|
||||
Reference in New Issue
Block a user