Records the two GET routes, what each panel consumes, and the three properties the surface is held to: an unreadable source reports unavailable rather than green, authorization is probed with for_execution=True so a Phase 1 refusal is never shown as an allow, and the control-plane database is opened mode=ro so reading status never creates it. Refs #655 #642 #658 #661 #662 #663 #633 #652 #653 #664 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
103 lines
4.7 KiB
Markdown
103 lines
4.7 KiB
Markdown
# Web Console: restart status, impact preview, and approval state (#667)
|
|
|
|
Phase 1 of the console restart surface. It consumes the #655 coordinator
|
|
substrate and displays it. It performs no restart, reload, drain, approval, or
|
|
process action, and it registers no write endpoint.
|
|
|
|
Issue #667's rollout is explicit — *status views first, write approval after the
|
|
backend gates are green* — and this change delivers only the status half.
|
|
|
|
## Surfaces
|
|
|
|
| Path | Method | Purpose |
|
|
|------|--------|---------|
|
|
| `/runtime/restart` | GET | Restart status page |
|
|
| `/api/v1/system/restart/status` | GET | Same snapshot as JSON |
|
|
|
|
Both accept an optional `restart_class` query parameter (default
|
|
`full_mcp_restart`). An unrecognised class is not an error: the coordinator
|
|
resolves it as unknown and fails closed, and the page shows the resulting deny.
|
|
|
|
Neither path accepts `POST`; a write attempt returns `405`, and a test asserts
|
|
it.
|
|
|
|
## What it shows
|
|
|
|
* **Impact preview (#658)** — verdict, blast radius, affected sessions, leases,
|
|
critical sections, mutations, and the counts behind them, evaluated
|
|
`dry_run=True` against live control-plane state.
|
|
* **Drain proof (#661)** — verification of a supplied proof: valid, clean,
|
|
expired, tampered, and the reasons behind a refusal.
|
|
* **Post-restart reconcile (#662)** — the most recent completion proof, its
|
|
overall status, and which dimensions still require follow-up.
|
|
* **Restart classes (#663)** — the least-privilege matrix, with *you may
|
|
request* and *you may execute* computed for the viewing role rather than for a
|
|
generic operator.
|
|
* **Approval controls (#633)** — the authorization state of
|
|
`system.restart_namespace` and `system.reload_namespace`.
|
|
* **Break-glass (#664)** — declared and marked unavailable; see below.
|
|
|
|
## Three rules this surface holds itself to
|
|
|
|
A status page that is wrong is worse than one that is missing, because an
|
|
operator acts on it. Three properties are enforced by tests, and each was
|
|
verified by reverting the guard and watching a test fail.
|
|
|
|
### An unreadable source reports unavailable, never green
|
|
|
|
Every source carries its own `SourceStatus`. Nothing substitutes a default,
|
|
placeholder, or self-comparison for a reading that failed. An unreadable
|
|
control-plane database yields `inventory_complete: false`, which the coordinator
|
|
itself turns into a fail-closed verdict, and the page says the blast radius is
|
|
unknown rather than showing an empty affected-sessions table.
|
|
|
|
An absent drain proof is reported as absent — not as a pass. The #661 gate
|
|
authorizes a restart only against a valid, unexpired, clean proof, so no proof
|
|
is precisely the state that gate denies on.
|
|
|
|
### Authorization is asked the way execution would ask it
|
|
|
|
Every probe passes `for_execution=True`.
|
|
|
|
Asked without it, an admin is `allowed` for `system.restart_namespace`. On a
|
|
control surface that reads as a live button. Asked the way an execution attempt
|
|
would ask, the same principal is refused `phase_not_active`, because the console
|
|
is in Phase 1 and the action is Phase 2. This surface reports the second answer.
|
|
|
|
`execution_enabled` is therefore `false` for every action and every role today,
|
|
and a test asserts that across the whole role matrix.
|
|
|
|
### The control-plane database is opened read-only
|
|
|
|
`ControlPlaneDB()` creates directories and runs migrations on construction — a
|
|
write. This surface never constructs one. It opens the sqlite file with
|
|
`mode=ro`, exactly as `webui/inventory.py` does, and treats a missing file as
|
|
missing authority rather than as an empty inventory.
|
|
|
|
The test that protects this points at a path inside a directory that already
|
|
exists, so a read-write `connect` would really create the file. A nested
|
|
missing-directory path would have passed for the wrong reason.
|
|
|
|
## Break-glass is declared, not offered
|
|
|
|
The break-glass workflow (#664) is not available on this branch's base. The
|
|
panel is rendered to operator-class roles as **unavailable**, naming the issue
|
|
that tracks it. It is not silently omitted, because an operator who has been
|
|
told a governance path exists needs to see that it is not wired here; and it is
|
|
not rendered as a control, because there is nothing behind it.
|
|
|
|
Unprivileged viewers see only a note that the surface is operator-class.
|
|
|
|
## Redaction and escaping
|
|
|
|
Every interpolated value passes through `_esc` (`html.escape(..., quote=True)`).
|
|
Free-form text and anything that can carry a filesystem path additionally passes
|
|
through `webui.inventory.scrub_text`, which redacts credential-shaped tokens
|
|
inside a string rather than only at its start. The impact payload is passed
|
|
through `webui.inventory.scrub` before rendering.
|
|
|
|
## Linkage
|
|
|
|
Parent #655 · extends #642 · consumes #658, #661, #662, #663 · RBAC #633 ·
|
|
console #631 · vision #652 · roadmap #653 · break-glass #664.
|