Adds `GET /api/v1/system/health`, a structured read-only health surface for
automated readiness checks, and keeps `/health` as the cheap liveness probe.
webui/system_health.py composes a DTO from fail-soft dependency probes: the
control-plane database, the local checkout, and — opt-in via `?deep=1` — live
Gitea reachability, each carrying status, reason, and probe latency. Required
probes drive readiness; the optional Gitea probe can only degrade overall
status, because local inventory stays serveable when the remote is
unreachable. A probe that did not run leaves readiness incomplete rather than
silently passing.
Read-only throughout: the control-plane database is opened through a `mode=ro`
URI because `ControlPlaneDB.__init__` creates directories and runs migrations,
which a health check must never do. No restart or reload control is exposed;
those are Phase 2 and #630 forbids process-kill recovery.
No unproven claims: `stale_runtime.mutation_safe` is true only when the
runtime, checkout, and remote commits are all known and equal, and MCP
namespaces always report `unproven` because a web process cannot exercise the
IDE-managed client path (#543). Probe details are redacted at the browser
boundary — URLs lose userinfo and query strings, credential-shaped text is
masked.
`/health` is expanded additively: every MVP key is retained, plus `started_at`,
`uptime_seconds`, and a pointer to the versioned API. The versioned route
returns 503 when not ready so automation can branch on the status code alone.
Verified at master 9eb0f29: focused file 40 passed / 11 subtests; `-k "webui or
health"` 230 passed / 159 subtests; full suite 4358 passed with the 11
pre-existing master-drift failures unchanged from the clean-master baseline.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
295 lines
13 KiB
Markdown
295 lines
13 KiB
Markdown
# Internal web UI — local development (#426)
|
|
|
|
Read-only MVP skeleton for the MCP Control Plane operator console. Gitea,
|
|
MCP capability gates, and `skills/llm-project-workflow/` remain the source of
|
|
truth; this UI only provides route stubs and layout.
|
|
|
|
## Prerequisites
|
|
|
|
- Python 3.11+ with project dependencies installed (`pip install -r requirements.txt`)
|
|
- No secrets in repo, config, or client bundle
|
|
|
|
## Start the server
|
|
|
|
From the repository root (or an issue worktree):
|
|
|
|
```bash
|
|
./scripts/run-webui
|
|
```
|
|
|
|
Or directly:
|
|
|
|
```bash
|
|
python3 -m webui
|
|
```
|
|
|
|
Optional environment variables:
|
|
|
|
| Variable | Default | Purpose |
|
|
|----------|---------|---------|
|
|
| `WEBUI_HOST` | `127.0.0.1` | Bind address (keep local for MVP) |
|
|
| `WEBUI_PORT` | `8765` | Listen port |
|
|
| `WEBUI_REPO_ROOT` | repository root | Prompt library workflow hash root |
|
|
| `WEBUI_PROJECT_REGISTRY` | packaged JSON | Project registry path |
|
|
| `GITEA_MCP_CONFIG` | unset | Server-side MCP profile config (never sent to browser) |
|
|
| `GITEA_MCP_PROFILE` | unset | Active MCP profile name (server-side only) |
|
|
|
|
See [webui-deployment.md](webui-deployment.md) for internal-only serving,
|
|
Cloudflare Access/WARP/VPN guidance, and unsafe bind overrides (#435).
|
|
|
|
See
|
|
[architecture/webui-control-plane-console-architecture-adr.md](architecture/webui-control-plane-console-architecture-adr.md)
|
|
for the console architecture: layer and authority boundaries, the redaction
|
|
boundary, `/api/v1/...` versioning, the target page map, and the phase gates
|
|
that govern when a write path may open (#632, epic #631).
|
|
|
|
## Routes (MVP)
|
|
|
|
| Path | Description |
|
|
|------|-------------|
|
|
| `/` | Home / operator overview |
|
|
| `/health` | JSON liveness (`status`, `service`, `mode`, `timestamp`, `uptime_seconds`) |
|
|
| `/api/v1/system/health` | Structured read-only system health (#634) |
|
|
| `/queue` | Live PR and issue queue dashboard (#429) |
|
|
| `/api/queue` | JSON queue export with pagination metadata |
|
|
| `/projects` | Project registry list (#427) |
|
|
| `/projects/{id}` | Project detail + onboarding checklist |
|
|
| `/api/projects` | JSON registry export |
|
|
| `/prompts` | Prompt library with per-prompt copy buttons (#428) |
|
|
| `/api/prompts` | JSON prompt export with workflow hashes |
|
|
| `/runtime` | MCP runtime health and stale detection (#430) |
|
|
| `/api/runtime` | JSON runtime health export |
|
|
| `/audit` | Report audit paste + validator preview (#431) |
|
|
| `/api/audit` | JSON validator preview (POST `report_text`, optional `task_kind`) |
|
|
| `/worktrees` | Worktree hygiene dashboard (#432) |
|
|
| `/api/worktrees` | JSON worktree scan with classifications and anomalies |
|
|
| `/actions` | Gated write-action registry — all disabled in MVP (#434) |
|
|
| `/api/actions` | JSON action registry with capability metadata |
|
|
| `/api/actions/{id}/preview` | Mutation ledger preview (GET, read-only) |
|
|
| `/leases` | Lease and collision visibility (#433) |
|
|
| `/api/leases` | JSON lease/collision export |
|
|
|
|
Most routes are GET-only. POST/PUT/PATCH/DELETE return `405` with
|
|
`read-only-mvp`, except `/audit` and `/api/audit` which accept POST for
|
|
local validator preview only (no Gitea mutations, no server-side storage).
|
|
|
|
## System health API (#634)
|
|
|
|
`GET /api/v1/system/health` is the structured, read-only health surface for
|
|
automated readiness checks. It is the first console API under the `/api/v1`
|
|
prefix; the unversioned MVP exports remain as compatibility aliases.
|
|
|
|
`/health` is unchanged for existing consumers — every MVP key is still present
|
|
— and now also carries `started_at`, `uptime_seconds`, and a
|
|
`system_health_api` pointer. It stays deliberately cheap and runs no dependency
|
|
probe, because answering readiness costs real work.
|
|
|
|
**Status codes.** `200` when ready, `503` when a required dependency failed or
|
|
was never probed. Automation can branch on the code without parsing the body.
|
|
|
|
**Query flags.** The Gitea check is a network call, so it is opt-in:
|
|
`GET /api/v1/system/health?deep=1` runs it and caches the result for
|
|
`WEBUI_HEALTH_PROBE_TTL_SECONDS` (default 15s) so dashboard polling does not
|
|
amplify into remote load. Without the flag that probe reports `skipped`.
|
|
|
|
**Dependencies.** `control_plane_db` and `repository` are required and drive
|
|
readiness. `gitea` is optional: when it fails the overall `status` degrades but
|
|
`readiness.ready` stays true, because local inventory is still serveable. Each
|
|
entry carries `status`, `detail`, `required`, and `latency_ms`.
|
|
|
|
Two honesty rules are worth knowing before reading the payload:
|
|
|
|
* `stale_runtime.mutation_safe` is true only when the runtime, checkout, and
|
|
remote-tracking commits are all known and equal. An unfetched remote is
|
|
reported as indeterminate, never as safe.
|
|
* `mcp_namespaces` entries are always `unproven`. A web process runs outside
|
|
the IDE-managed MCP client and cannot invoke a namespace tool, so per #543
|
|
only a `client_namespace` probe can prove that path.
|
|
|
|
Sample response (abridged, healthy):
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "mcp-control-plane-webui",
|
|
"mode": "read-only",
|
|
"api": "/api/v1/system/health",
|
|
"timestamp": "2026-07-22T11:04:18.512034+00:00",
|
|
"readiness": { "ready": true, "complete": true, "reasons": [] },
|
|
"version": {
|
|
"git_sha": "620ed6e9a9550b8da2ceb82d9ab8744e8920490f",
|
|
"git_describe": "v1.1.0-898-g620ed6e",
|
|
"control_plane_schema_version": 4,
|
|
"python_version": "3.14.5",
|
|
"known": true
|
|
},
|
|
"process": { "started_at": "2026-07-22T10:58:02.114+00:00", "uptime_seconds": 376.4 },
|
|
"deep_probes_requested": false,
|
|
"dependencies": [
|
|
{
|
|
"name": "control_plane_db",
|
|
"kind": "sqlite",
|
|
"status": "ok",
|
|
"detail": "schema v4 readable",
|
|
"required": true,
|
|
"healthy": true,
|
|
"latency_ms": 1.482,
|
|
"metadata": { "schema_version": 4, "active_leases": 3 }
|
|
},
|
|
{ "name": "repository", "kind": "git", "status": "ok", "required": true, "healthy": true },
|
|
{ "name": "gitea", "kind": "http", "status": "skipped", "required": false, "healthy": false }
|
|
],
|
|
"mcp_namespaces": [
|
|
{ "namespace": "gitea-author", "required_tool": "gitea_whoami", "status": "unproven" }
|
|
],
|
|
"stale_runtime": { "stale": false, "determinable": true, "mutation_safe": true, "reasons": [] },
|
|
"probe_errors": []
|
|
}
|
|
```
|
|
|
|
No restart, reload, or process-kill control is exposed here: those are Phase 2
|
|
at the earliest, and #630 forbids process-kill recovery outright. Every probe
|
|
opens its subject read-only — the control-plane database is opened through a
|
|
`mode=ro` URI so a health check can never create or migrate a schema.
|
|
|
|
## Report audit (#431)
|
|
|
|
Paste an LLM final report at `/audit` or POST JSON to `/api/audit`. The UI
|
|
reuses `final_report_validator` and review schema checks to surface missing
|
|
proof fields, wrong validation vocabulary, mutation contradictions, and a
|
|
suggested next prompt or issue-comment draft. Task kind can be auto-detected
|
|
or selected explicitly.
|
|
|
|
## Project registry (#427)
|
|
|
|
Versioned registry file: `webui/data/projects.registry.json` (schema version `1`).
|
|
|
|
Override path with `WEBUI_PROJECT_REGISTRY` when operators keep a machine-local
|
|
copy outside git. The registry stores repo identity, remotes, profile names,
|
|
workflow/schema path references, and onboarding checklist steps — never tokens
|
|
or credentials.
|
|
|
|
Seed entry: **Gitea-Tools** on `https://gitea.prgs.cc` with `prgs-author`,
|
|
`prgs-reviewer`, and `prgs-reconciler` profiles.
|
|
|
|
## Prompt library (#428)
|
|
|
|
Prompts are generated at load time from canonical workflow files under
|
|
`skills/llm-project-workflow/workflows/`. SHA-256 hashes are computed from
|
|
`WEBUI_REPO_ROOT` (defaults to the repository root). Prompt bodies are short
|
|
copy/paste starters; canonical workflow files remain the only full policy
|
|
source.
|
|
|
|
## Live queue dashboard (#429)
|
|
|
|
`/queue` loads open PRs and issues for the default registry project (seed:
|
|
**Gitea-Tools** on `https://gitea.prgs.cc`) using existing `gitea_auth` read
|
|
credentials. The UI surfaces pagination proof (returned count, pages fetched,
|
|
`has_more`, `inventory_complete`) and classification badges (`claimed`,
|
|
`blocked`, `in-review`, `duplicate`) when evidence exists.
|
|
|
|
If credentials are missing or the fetch fails, the page shows an explicit error
|
|
instead of an empty queue (fail closed).
|
|
|
|
## Gated actions (#434)
|
|
|
|
`/actions` registers future write actions (claim, comment, review, merge,
|
|
delete branch, create PR/issue). Each entry declares the MCP tool, required
|
|
permission, and profile role from `task_capability_map.py` — aligned with
|
|
`gitea_resolve_task_capability`. Buttons are disabled; previews always render
|
|
a mutation ledger. Direct `attempt_action` calls fail closed without invoking
|
|
MCP tools.
|
|
|
|
## Worktree hygiene (#432)
|
|
|
|
`/worktrees` scans local `branches/` directories and registered git worktrees.
|
|
Each entry is classified (`active-pr`, `active-issue`, `dirty`, `stale-clean`,
|
|
`detached-review`, `unsafe-unknown`, `orphan`). Missing preserved worktrees
|
|
referenced by the issue lock file are flagged as anomalies (#404). The page
|
|
includes a copy/paste canonical cleanup prompt only — no deletion actions.
|
|
|
|
Override scan root with `WEBUI_REPO_ROOT` (defaults to repository root).
|
|
|
|
## Lease visibility (#433)
|
|
|
|
`/leases` surfaces read-only lease and collision state: local issue lock file,
|
|
in-progress claim inventory (#268), reviewer PR lease comments when present
|
|
(`<!-- mcp-review-lease:v1 -->`, #407), duplicate open PRs per issue (#400),
|
|
and duplicate local branches per issue. Links to collision-history backend
|
|
issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.
|
|
|
|
## Runtime health (#430)
|
|
|
|
`/runtime` surfaces read-only MCP/runtime diagnostics for the default registry
|
|
project: active profile and role kind, authenticated identity (when credentials
|
|
are available), config model/mode, local vs remote `master` SHA sync, shell
|
|
health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the
|
|
checkout is behind merged safety-gate changes. Restart guidance links to #420;
|
|
no tokens or MCP restart actions are exposed.
|
|
|
|
## Deployment boundary (#435)
|
|
|
|
MVP serves on loopback by default. Binding `0.0.0.0` or `::` is **refused**
|
|
unless `WEBUI_ALLOW_PUBLIC_BIND=1`. Non-loopback hosts log a warning unless
|
|
`WEBUI_ALLOW_REMOTE_BIND=1`. `GET /health` exposes `deployment` metadata.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_gated_actions.py -q
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q
|
|
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_deployment_boundary.py -q
|
|
|
|
## Tests (#436)
|
|
|
|
Run the full hermetic web UI suite (all `test_webui_*.py` modules):
|
|
|
|
```bash
|
|
./scripts/test-webui
|
|
```
|
|
|
|
CI / Jenkins multibranch can call the path-filtered gate (runs only when the
|
|
diff touches `webui/`, `tests/test_webui_*`, or web UI docs/scripts):
|
|
|
|
```bash
|
|
./scripts/ci-webui-check
|
|
WEBUI_CI_FORCE=1 ./scripts/ci-webui-check # always run
|
|
```
|
|
|
|
`scripts/test-webui` sets `WEBUI_TEST_OFFLINE=1` by default. In that mode the
|
|
queue, lease, and runtime routes use empty offline snapshots instead of Gitea
|
|
credentials, so CI can run without MCP daemon credential access. Set
|
|
`WEBUI_TEST_OFFLINE=0` only when deliberately validating live fetch behavior.
|
|
|
|
Or invoke unittest directly:
|
|
|
|
```bash
|
|
python3 -m unittest discover -s tests -p 'test_webui_*.py' -q
|
|
```
|
|
|
|
## Lease visibility (#433)
|
|
|
|
`/leases` surfaces read-only lease and collision state: local issue lock file,
|
|
in-progress claim inventory (#268), reviewer PR lease comments when present
|
|
(`<!-- mcp-review-lease:v1 -->`, #407), duplicate open PRs per issue (#400),
|
|
and duplicate local branches per issue. Links to collision-history backend
|
|
issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.
|
|
|
|
## Runtime health (#430)
|
|
|
|
`/runtime` surfaces read-only MCP/runtime diagnostics for the default registry
|
|
project: active profile and role kind, authenticated identity (when credentials
|
|
are available), config model/mode, local vs remote `master` SHA sync, shell
|
|
health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the
|
|
checkout is behind merged safety-gate changes. Restart guidance links to #420;
|
|
no tokens or MCP restart actions are exposed.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
|
|
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q
|
|
```
|