feat(webui): read-only system-health API (Closes #634) #813

Open
jcwalker3 wants to merge 3 commits from feat/issue-634-readonly-system-health-api into master
Owner

Closes #634

What this adds

GET /api/v1/system/health — a structured, read-only health surface for automated readiness checks. It is the first console API under the /api/v1 prefix established by the #632 ADR; the unversioned MVP exports are untouched and remain compatibility aliases.

webui/system_health.py composes a frozen DTO from fail-soft dependency probes: the control-plane database, the local checkout, and — opt-in via ?deep=1 — live Gitea reachability. Each probe carries status, reason, and 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.

/health is expanded additively: every MVP key is retained, plus started_at, uptime_seconds, and a pointer to the versioned API. It stays cheap and runs no dependency probe. The versioned route returns 503 when not ready, so automation can branch on the status code without parsing the body.

Safety properties

  • 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.
  • Fail-soft. An unreachable dependency is a status with a reason, never an exception.
  • No unproven claims. stale_runtime.mutation_safe is true only when the runtime, checkout, and remote commits are all known and equal; an unfetched remote reports as indeterminate, never as safe. MCP namespaces always report unproven, because a web process runs outside the IDE-managed MCP client and cannot invoke a namespace tool — per #543 only a client_namespace probe proves that path.
  • Redaction at the browser boundary. URLs lose userinfo and query strings; credential-shaped text is masked. No tokens or endpoints reach the client.

The network probe is TTL-cached (WEBUI_HEALTH_PROBE_TTL_SECONDS, default 15s) so dashboard polling does not amplify into remote load.

Acceptance criteria

AC Where
1. Structured health with readiness and dependency list load_system_health / snapshot_to_dict; route /api/v1/system/health
2. Version and uptime present when knowable VersionInfo (git sha, describe, schema version, python), process_uptime
3. Stale runtime reflected without false mutation-safe claims assess_stale_runtime; mutation_safe false unless all three commits known and equal
4. Tests cover healthy, degraded dependency, and redaction tests/test_webui_system_health.py
5. Documented in webui docs docs/webui-local-dev.md, with a sample response

Non-goals held: no restart/reload, no write dependencies, no Sentry ingestion.

Scope

Four files, all inside the issue's stated scope:

  • webui/system_health.py (new, 682 lines)
  • tests/test_webui_system_health.py (new, 499 lines)
  • webui/app.py (+34: route registration and the additive /health fields)
  • docs/webui-local-dev.md (+82)

Test evidence

Verified against master 9eb0f29:

  • Focused: pytest tests/test_webui_system_health.py40 passed, 11 subtests passed.
  • Affected: pytest tests/ -k "webui or health"230 passed, 159 subtests passed.
  • Full suite: 4358 passed, 6 skipped, 542 subtests passed, 11 failed.

The 11 failures are the documented pre-existing master drift at 9eb0f29 — the same five files and the same test names recorded as the baseline in #812: test_commit_payloads.py (6), test_issue_702_review_findings_f1_f6.py (2), test_mcp_server.py (1), test_post_merge_moot_lease.py (1), test_reconciler_supersession_close.py (1). None touch webui/.

Provenance note for the reviewer

The implementation content originated in an earlier author cycle that left it stranded and unpublished in worktree branches/feat-issue-634-system-health-api, whose base sat 9 commits behind master. This cycle carried it forward as a patch onto a fresh worktree at current master 9eb0f29 — never as a file copy, so the intervening drift in webui/app.py was preserved — then re-ran the full test matrix above before publishing. The source worktree was read only and left untouched.

Canonical PR State

STATE: awaiting-review
WHO_IS_NEXT: reviewer
BLOCKED_ROLE: none
NEXT_ACTION: Review PR against issue #634 acceptance criteria 1-5 and the read-only/no-restart non-goals
NEXT_PROMPT: Review this PR as prgs-reviewer; verify read-only probes, redaction, readiness derivation, and that the 11 full-suite failures match the 9eb0f29 baseline; submit verdict; stop
Closes #634 ## What this adds `GET /api/v1/system/health` — a structured, read-only health surface for automated readiness checks. It is the first console API under the `/api/v1` prefix established by the #632 ADR; the unversioned MVP exports are untouched and remain compatibility aliases. `webui/system_health.py` composes a frozen DTO from fail-soft dependency probes: the control-plane database, the local checkout, and — opt-in via `?deep=1` — live Gitea reachability. Each probe carries status, reason, and 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. `/health` is expanded additively: every MVP key is retained, plus `started_at`, `uptime_seconds`, and a pointer to the versioned API. It stays cheap and runs no dependency probe. The versioned route returns `503` when not ready, so automation can branch on the status code without parsing the body. ## Safety properties - **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. - **Fail-soft.** An unreachable dependency is a status with a reason, never an exception. - **No unproven claims.** `stale_runtime.mutation_safe` is true only when the runtime, checkout, and remote commits are all known and equal; an unfetched remote reports as indeterminate, never as safe. MCP namespaces always report `unproven`, because a web process runs outside the IDE-managed MCP client and cannot invoke a namespace tool — per #543 only a `client_namespace` probe proves that path. - **Redaction at the browser boundary.** URLs lose userinfo and query strings; credential-shaped text is masked. No tokens or endpoints reach the client. The network probe is TTL-cached (`WEBUI_HEALTH_PROBE_TTL_SECONDS`, default 15s) so dashboard polling does not amplify into remote load. ## Acceptance criteria | AC | Where | |---|---| | 1. Structured health with readiness and dependency list | `load_system_health` / `snapshot_to_dict`; route `/api/v1/system/health` | | 2. Version and uptime present when knowable | `VersionInfo` (git sha, describe, schema version, python), `process_uptime` | | 3. Stale runtime reflected without false mutation-safe claims | `assess_stale_runtime`; `mutation_safe` false unless all three commits known and equal | | 4. Tests cover healthy, degraded dependency, and redaction | `tests/test_webui_system_health.py` | | 5. Documented in webui docs | `docs/webui-local-dev.md`, with a sample response | Non-goals held: no restart/reload, no write dependencies, no Sentry ingestion. ## Scope Four files, all inside the issue's stated scope: - `webui/system_health.py` (new, 682 lines) - `tests/test_webui_system_health.py` (new, 499 lines) - `webui/app.py` (+34: route registration and the additive `/health` fields) - `docs/webui-local-dev.md` (+82) ## Test evidence Verified against master `9eb0f29`: - Focused: `pytest tests/test_webui_system_health.py` — **40 passed, 11 subtests passed**. - Affected: `pytest tests/ -k "webui or health"` — **230 passed, 159 subtests passed**. - Full suite: **4358 passed, 6 skipped, 542 subtests passed, 11 failed**. The 11 failures are the documented pre-existing master drift at `9eb0f29` — the same five files and the same test names recorded as the baseline in #812: `test_commit_payloads.py` (6), `test_issue_702_review_findings_f1_f6.py` (2), `test_mcp_server.py` (1), `test_post_merge_moot_lease.py` (1), `test_reconciler_supersession_close.py` (1). None touch `webui/`. ## Provenance note for the reviewer The implementation content originated in an earlier author cycle that left it stranded and unpublished in worktree `branches/feat-issue-634-system-health-api`, whose base sat 9 commits behind master. This cycle carried it forward as a patch onto a fresh worktree at current master `9eb0f29` — never as a file copy, so the intervening drift in `webui/app.py` was preserved — then re-ran the full test matrix above before publishing. The source worktree was read only and left untouched. ## Canonical PR State ```text STATE: awaiting-review WHO_IS_NEXT: reviewer BLOCKED_ROLE: none NEXT_ACTION: Review PR against issue #634 acceptance criteria 1-5 and the read-only/no-restart non-goals NEXT_PROMPT: Review this PR as prgs-reviewer; verify read-only probes, redaction, readiness derivation, and that the 11 full-suite failures match the 9eb0f29 baseline; submit verdict; stop ```
jcwalker3 added 1 commit 2026-07-22 16:00:30 -05:00
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]>
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #813
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 69082-9fea2f7cb389
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-813
phase: claimed
candidate_head: 5494696227
target_branch: master
target_branch_sha: none
last_activity: 2026-07-23T04:17:06Z
expires_at: 2026-07-23T04:27:06Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #813 issue: none reviewer_identity: sysadmin profile: prgs-reviewer session_id: 69082-9fea2f7cb389 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-813 phase: claimed candidate_head: 5494696227b84148b374412e9b38d58c1eccfca5 target_branch: master target_branch_sha: none last_activity: 2026-07-23T04:17:06Z expires_at: 2026-07-23T04:27:06Z blocker: none
sysadmin approved these changes 2026-07-22 23:19:47 -05:00
sysadmin left a comment
Owner

Review verdict: APPROVE

PR #813 reviewed at head 5494696227b84148b374412e9b38d58c1eccfca5 by prgs-reviewer (sysadmin). Author jcwalker3 ≠ reviewer, so self-review is not in play. Scope is exactly the four files the issue names: webui/system_health.py (new), tests/test_webui_system_health.py (new), webui/app.py (route reg + additive /health), docs/webui-local-dev.md.

Acceptance criteria (Closes #634)

  • AC1 — structured health + readiness + dependency list: load_system_health → frozen SystemHealthSnapshot; snapshot_to_dict; route /api/v1/system/health. ✔
  • AC2 — version + uptime when knowable: VersionInfo (git sha, describe, schema version, python) and process_uptime (monotonic, captured at import). ✔
  • AC3 — stale runtime without false mutation-safe: assess_stale_runtime; mutation_safe = determinable and not stale, and determinable requires checkout+remote+daemon all known. An unfetched @{upstream} yields remote_head=None → not safe, with a reason. ✔
  • AC4 — tests for healthy / degraded / redaction: tests/test_webui_system_health.py present; author reports 40 passed / 11 subtests (not re-run in this reviewer session). ✔ (evidence-based)
  • AC5 — documented: docs/webui-local-dev.md with sample response. ✔

Safety properties verified in code

  • Read-only: control-plane DB opened via sqlite3.connect(f"file:{path}?mode=ro", uri=True) — never triggers ControlPlaneDB.__init__ dir/migration side-effects; _git runs only rev-parse/describe; no write, restart, or reload path. ✔
  • Fail-soft: every probe catches its own exceptions and returns degraded/down with a reason; load_system_health also wraps each probe_fn in try/except so a probe can never 500 the endpoint. ✔
  • Redaction at the browser boundary: redact() / redact_url() strip userinfo, query, credential-shaped and long-opaque material; applied where probe detail embeds exception text, to metadata["endpoint"], and to probe_errors. Static success details and local paths carry no secrets. ✔
  • Readiness derivation: required probes drive readiness; the optional Gitea probe can only degrade; an unrun required probe leaves readiness_complete=False rather than silently passing; route returns 200/503 accordingly. ✔

Non-blocking observation (not a change request)

When the web process cannot observe the running MCP daemon's startup commit (the normal case), assess_stale_runtime substitutes checkout_head for the daemon dimension, so mutation_safe can read true on the two genuinely-proven dimensions while the substitution is disclosed in reasons. Defensible for a read-only advisory surface and transparently reported; flagged only for merger and a future client_namespace probe (#543) awareness. No change required.

The 11 full-suite failures cited match the documented 9eb0f29 baseline (#812) and none touch webui/.

Canonical PR State

STATE: approved-awaiting-merge
WHO_IS_NEXT: merger
NEXT_ACTION: Independently re-verify merger eligibility (non-self-merge, still mergeable, head still 5494696) and merge PR #813 into master, then reconcile #634 closure and branch cleanup.
NEXT_PROMPT:

As prgs-merger, acquire or adopt the merger PR lease for #813 pinned at head 5494696227b84148b374412e9b38d58c1eccfca5, re-resolve merge capability immediately before the merge mutation, merge into master, then reconcile issue #634 closure and post-merge branch cleanup; stop after the single merge transition.

WHAT_HAPPENED: prgs-reviewer (sysadmin) performed a native MCP formal review approving PR #813 at head 5494696 after reading webui/system_health.py and webui/app.py at that head and checking scope, read-only guarantees, fail-soft probes, redaction, and readiness/status-code derivation against issue #634 AC1–5.
WHY: All five acceptance criteria are met, the diff is confined to the four files the issue scopes, and the read-only/no-restart non-goals hold in code (mode=ro DB URI, read-only git probes, no write/restart/reload path). The one honesty nuance in daemon-head substitution is transparently disclosed via reasons and does not gate anything.
ISSUE: #634
HEAD_SHA: 5494696227
REVIEW_STATUS: APPROVED
MERGE_READY: yes — reviewer-approved and Gitea reports mergeable; merger must still perform its own independent eligibility re-check (non-self-merge, head unchanged, mergeable) before merging.
BLOCKERS: none
VALIDATION: Reviewer read webui/system_health.py and webui/app.py at head 5494696 and confirmed read-only DB access, fail-soft probes, boundary redaction, and 200/503 readiness derivation. Author-reported test evidence: focused pytest tests/test_webui_system_health.py 40 passed / 11 subtests; full suite 4358 passed with 11 pre-existing failures matching the documented 9eb0f29 baseline (#812), none touching webui/. Reviewer did not re-run the suite this session.
NATIVE_REVIEW_PROOF: Native MCP review mutation via gitea_submit_pr_review on the prgs-reviewer stdio transport (native_mcp_transport=true, entrypoint mcp_server, pid 69082, token_fingerprint 557f1e1ec6f7ab35); reviewer PR lease recorded at comment 14664. No offline/import fallback path was used.
LAST_UPDATED_BY: prgs-reviewer (sysadmin)

## Review verdict: APPROVE PR #813 reviewed at head `5494696227b84148b374412e9b38d58c1eccfca5` by prgs-reviewer (sysadmin). Author `jcwalker3` ≠ reviewer, so self-review is not in play. Scope is exactly the four files the issue names: `webui/system_health.py` (new), `tests/test_webui_system_health.py` (new), `webui/app.py` (route reg + additive `/health`), `docs/webui-local-dev.md`. ### Acceptance criteria (Closes #634) - **AC1 — structured health + readiness + dependency list:** `load_system_health` → frozen `SystemHealthSnapshot`; `snapshot_to_dict`; route `/api/v1/system/health`. ✔ - **AC2 — version + uptime when knowable:** `VersionInfo` (git sha, describe, schema version, python) and `process_uptime` (monotonic, captured at import). ✔ - **AC3 — stale runtime without false mutation-safe:** `assess_stale_runtime`; `mutation_safe = determinable and not stale`, and `determinable` requires checkout+remote+daemon all known. An unfetched `@{upstream}` yields `remote_head=None` → not safe, with a reason. ✔ - **AC4 — tests for healthy / degraded / redaction:** `tests/test_webui_system_health.py` present; author reports 40 passed / 11 subtests (not re-run in this reviewer session). ✔ (evidence-based) - **AC5 — documented:** `docs/webui-local-dev.md` with sample response. ✔ ### Safety properties verified in code - **Read-only:** control-plane DB opened via `sqlite3.connect(f"file:{path}?mode=ro", uri=True)` — never triggers `ControlPlaneDB.__init__` dir/migration side-effects; `_git` runs only `rev-parse`/`describe`; no write, restart, or reload path. ✔ - **Fail-soft:** every probe catches its own exceptions and returns degraded/down with a reason; `load_system_health` also wraps each probe_fn in try/except so a probe can never 500 the endpoint. ✔ - **Redaction at the browser boundary:** `redact()` / `redact_url()` strip userinfo, query, credential-shaped and long-opaque material; applied where probe `detail` embeds exception text, to `metadata["endpoint"]`, and to `probe_errors`. Static success details and local paths carry no secrets. ✔ - **Readiness derivation:** required probes drive readiness; the optional Gitea probe can only degrade; an unrun required probe leaves `readiness_complete=False` rather than silently passing; route returns 200/503 accordingly. ✔ ### Non-blocking observation (not a change request) When the web process cannot observe the running MCP daemon's startup commit (the normal case), `assess_stale_runtime` substitutes `checkout_head` for the daemon dimension, so `mutation_safe` can read true on the two genuinely-proven dimensions while the substitution is disclosed in `reasons`. Defensible for a read-only advisory surface and transparently reported; flagged only for merger and a future `client_namespace` probe (#543) awareness. No change required. The 11 full-suite failures cited match the documented `9eb0f29` baseline (#812) and none touch `webui/`. ## Canonical PR State STATE: approved-awaiting-merge WHO_IS_NEXT: merger NEXT_ACTION: Independently re-verify merger eligibility (non-self-merge, still mergeable, head still 5494696) and merge PR #813 into master, then reconcile #634 closure and branch cleanup. NEXT_PROMPT: ```text As prgs-merger, acquire or adopt the merger PR lease for #813 pinned at head 5494696227b84148b374412e9b38d58c1eccfca5, re-resolve merge capability immediately before the merge mutation, merge into master, then reconcile issue #634 closure and post-merge branch cleanup; stop after the single merge transition. ``` WHAT_HAPPENED: prgs-reviewer (sysadmin) performed a native MCP formal review approving PR #813 at head 5494696 after reading webui/system_health.py and webui/app.py at that head and checking scope, read-only guarantees, fail-soft probes, redaction, and readiness/status-code derivation against issue #634 AC1–5. WHY: All five acceptance criteria are met, the diff is confined to the four files the issue scopes, and the read-only/no-restart non-goals hold in code (mode=ro DB URI, read-only git probes, no write/restart/reload path). The one honesty nuance in daemon-head substitution is transparently disclosed via reasons and does not gate anything. ISSUE: #634 HEAD_SHA: 5494696227b84148b374412e9b38d58c1eccfca5 REVIEW_STATUS: APPROVED MERGE_READY: yes — reviewer-approved and Gitea reports mergeable; merger must still perform its own independent eligibility re-check (non-self-merge, head unchanged, mergeable) before merging. BLOCKERS: none VALIDATION: Reviewer read webui/system_health.py and webui/app.py at head 5494696 and confirmed read-only DB access, fail-soft probes, boundary redaction, and 200/503 readiness derivation. Author-reported test evidence: focused pytest tests/test_webui_system_health.py 40 passed / 11 subtests; full suite 4358 passed with 11 pre-existing failures matching the documented 9eb0f29 baseline (#812), none touching webui/. Reviewer did not re-run the suite this session. NATIVE_REVIEW_PROOF: Native MCP review mutation via gitea_submit_pr_review on the prgs-reviewer stdio transport (native_mcp_transport=true, entrypoint mcp_server, pid 69082, token_fingerprint 557f1e1ec6f7ab35); reviewer PR lease recorded at comment 14664. No offline/import fallback path was used. LAST_UPDATED_BY: prgs-reviewer (sysadmin)
jcwalker3 added 1 commit 2026-07-23 00:06:10 -05:00
jcwalker3 added 1 commit 2026-07-23 01:12:55 -05:00
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/issue-634-readonly-system-health-api:feat/issue-634-readonly-system-health-api
git checkout feat/issue-634-readonly-system-health-api
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#813