feat(webui): read-only workflow policy & guardrail visibility (Closes #646)
Phase 3 child of the Web Console epic #631. Operators can now see the active workflow policy/guardrail configuration from the console instead of reading the repo tree. - webui/policy_inventory.py (new): redacted, machine-readable guardrail inventory. One row per major guardrail (role separation/RBAC, lease rules, worktree binding, merge confirmation, redaction, contamination, allocator policy, audit logging, mutation gating) with source pointers (file/module/doc) and a compact active projection from the existing safe policy accessors. Fail-soft per entry; whole payload run through console_redaction before emit; diff vs documented default where feasible. - webui/policy_views.py (new): HTML cards with source pointers, active config, and the documented-default diff; read-only page copy, no forms. - webui/app.py: register GET /policy and GET /api/v1/policy (additive). - webui/layout.py: add Policy nav item. - tests/test_webui_policy_visibility.py (new): guardrail presence + source pointers (AC1), redaction incl. planted-secret masking and scan_for_secrets (AC2/AC3), read-only page + no-mutation routes (AC4), fail-soft rendering. - docs/webui-local-dev.md: route table + read-only policy-visibility section. Read-only throughout; no policy editing, no gate-weakening toggle, secrets redacted. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -45,6 +45,8 @@ from webui.worktree_scanner import load_hygiene_snapshot, snapshot_to_dict as wo
|
||||
from webui.worktree_views import render_worktrees_page
|
||||
from webui.runtime_health import load_runtime_snapshot, snapshot_to_dict as runtime_snapshot_to_dict
|
||||
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
|
||||
|
||||
_READ_ONLY_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
|
||||
_AUDIT_MUTATION_PATHS = frozenset({"/audit", "/api/audit"})
|
||||
@@ -68,6 +70,7 @@ async def home(_request: Request) -> HTMLResponse:
|
||||
"<li><strong>Projects</strong> — registry and onboarding (#427)</li>"
|
||||
"<li><strong>Prompts</strong> — canonical workflow prompt library (#428)</li>"
|
||||
"<li><strong>Runtime</strong> — MCP health and stale-runtime detection (#430)</li>"
|
||||
"<li><strong>Policy</strong> — workflow guardrail configuration visibility (#646)</li>"
|
||||
"<li><strong>Audit</strong> — final-report paste and validator preview (#431)</li>"
|
||||
"<li><strong>Worktrees</strong> — branch hygiene dashboard (#432)</li>"
|
||||
"<li><strong>Leases</strong> — collision and lease visibility (#433)</li>"
|
||||
@@ -210,6 +213,17 @@ async def api_runtime(_request: Request) -> JSONResponse:
|
||||
return JSONResponse(runtime_snapshot_to_dict(load_runtime_snapshot()))
|
||||
|
||||
|
||||
async def policy(_request: Request) -> HTMLResponse:
|
||||
snapshot = load_policy_inventory()
|
||||
return HTMLResponse(
|
||||
render_page(title="Policy", body_html=render_policy_page(snapshot))
|
||||
)
|
||||
|
||||
|
||||
async def api_v1_policy(_request: Request) -> JSONResponse:
|
||||
return JSONResponse(policy_snapshot_to_dict(load_policy_inventory()))
|
||||
|
||||
|
||||
async def _parse_audit_form(request: Request) -> tuple[str, str | None]:
|
||||
if request.method == "GET":
|
||||
return "", None
|
||||
@@ -415,6 +429,8 @@ def create_app(*, bind_host: str | None = None) -> Starlette:
|
||||
Route("/api/prompts", api_prompts, methods=["GET"]),
|
||||
Route("/runtime", runtime, methods=["GET"]),
|
||||
Route("/api/runtime", api_runtime, methods=["GET"]),
|
||||
Route("/policy", policy, methods=["GET"]),
|
||||
Route("/api/v1/policy", api_v1_policy, methods=["GET"]),
|
||||
Route("/audit", audit, methods=["GET", "POST"]),
|
||||
Route("/api/audit", api_audit, methods=["GET", "POST"]),
|
||||
Route("/worktrees", worktrees, methods=["GET"]),
|
||||
|
||||
Reference in New Issue
Block a user