phase: 03_system_per_host_panels
**Phase 03 final verification pass — all 4 tasks already in `complete/`; verified criteria, fixed 3 pre-existing defects.**
- **Verified live** (headless Chromium + curl against `:8090`, all 6 sources `ok`): 3 panels × six heroes in order `cpu,mem,temp,disk,gpus,net`; charts per panel `util,net[,gpu]` (gateway: no GPU chart/hole); legends name metrics only — no legend names a foreign host; every `<svg>` `role="img"` + host-scoped `aria-label`; 2 sections, 0 `<select>`; `grep -rn "renderCores|core_usage" src/dashboards/web/` → nothing; console error-free; new shot `data/logs/final-system-panels.png`.
- **Defect fixed (credential hygiene, invariant 6)**: `collectors.py` put the *dialed* URL into every `UpstreamError`, and that string lands in the health record (`/api/health`) and the `event` table (`/api/events` → page); `/api/config` also echoed `GUFO_BASE_URL`/`VLLM_BASE_URL` verbatim. Added `config.strip_userinfo()` (now what `Target.display_url` uses) and redacted all message/config URLs. Tests: `test_a_failing_host_never_echoes_its_token_in_the_message` (×3 paths), `test_a_failing_engine_never_echoes_credentials_in_its_url`, `test_a_dead_host_never_writes_its_token_into_the_feed`, `test_engine_urls_are_redacted_the_same_way_hosts_are`, `test_strip_userinfo_is_the_one_redaction_every_url_leaves_by`. (`parse_targets` already moves `HOST_TARGETS` userinfo into a header, so this is a backstop for hosts and a real fix for engine URLs.)
- **Defect fixed (invariant 2)**: `/system` footnote printed `Δ{HOST_INTERVAL}` as the measurement; it now prints each host's measured `window_seconds` (range when they disagree) — `rates over Δ10.0s · poll 10s`. Pinned by `test_system_footnote_prints_the_window_it_actually_measured`. Also fixed the page's stale "window in each header" note and the empty-state's `--token` (argv) advice → `--token-file`.
- `bash .agents/validate.sh` → **EXIT=0**: **472 passed, 7 skipped**, coverage **96.87%** (floor 94), `ruff check src tests agent` clean, `pyright` 0 errors, `node --check` clean · `uv run pytest -m live --live` → **7 passed** · `uv pip list --outdated` empty.
- Criteria: no multi-host legend ✅ · no per-core visual ✅ · six heroes/panel incl. no-GPU `n/a` uncoloured ✅ · per-core history still queryable ✅ (354 pts) · ≥3 shape screenshots ✅ (`system-driveripper/deskwork/gateway.png` + dark/light/stale) · pytest+coverage+lint ✅ · phases 01/02 unchanged (their pin tests + `/api/summary` shape tests pass; live UPS gap in AGENTS.md re-confirmed read-only against real upsd) ✅.
- **Docs**: AGENTS.md — invariant 6 rewritten to cover message/event redaction, `collectors.py` line, new "Δ is a measurement" rule, "hosts answering total" → "nine-figure fleet-totals panel" (the subtitle still counts hosts), test count 452→472, coverage 96.8→96.9, rewrapped an over-long line. README.md — same fleet-totals wording, `RATE_WINDOW_SECONDS` now covers every measured rate (not just tokens), agent route list completed (`/`, `/schema`, `?top=N`), `LIST CMD` in the diagram, new paragraph on URL redaction.
- **Deviation**: the criterion's literal `curl …/api/system/history?host=…&metric=cpu.usage_pct@core7` uses singular params that endpoint has never accepted (it takes `hosts=`/`metrics=` and ignores unknown ones). Verified 354 points via `…system/history?hosts=driveripper&metrics=cpu.usage_pct@core7&range=1h` and `/api/history?source=host&host=…&metric=…`; I did **not** add singular param aliases (unauthorized API surface). No live infra changes made — reads only.
- **Next pending phase**: `04_llm_chart_readability`.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# Verification notes — `/system` on every host shape
|
||||
|
||||
**Date:** 2026-10-17 (dev loop: tunnels 9121/9122/9123 + upsd 3494 all OK; dev-server
|
||||
`/api/health` all six sources `ok` before and after verification)
|
||||
|
||||
## What was checked, on live data
|
||||
|
||||
Verified against `http://127.0.0.1:8090/system` with all three real agents answering, in a
|
||||
real browser (headless Chromium/Playwright), DOM assertions plus screenshots.
|
||||
|
||||
| Host (shape) | Result | Screenshot |
|
||||
| --- | --- | --- |
|
||||
| `driveripper` (discrete Radeon AI PRO R9700) | GPU hero: `3 %` ok, `.sub` = "Radeon AI PRO R9700 · VRAM 30.5 GiB / 31.9 GiB · pool 33 %"; util/net/GPU charts all present inside the panel | `system-driveripper.png` (dark), `system-driveripper-light.png` |
|
||||
| `deskwork` (Strix Halo APU, two memory pools) | GPU hero busy went 82 % ok → 85 % warn → 99 % fail across the session, so threshold colour visibly moves; `.sub` = "Radeon 8060S Graphics · VRAM 172.7 MiB / 512.0 MiB · pool 78 %" — small dedicated VRAM and high shared pool read as two facts, not a contradiction; MEM 91 % and TEMP ~83 °C amber | `system-deskwork.png` |
|
||||
| `gateway` (no GPU, no CPU temp sensor) | CPU TEMP `n/a` with **no** severity class; GPU slot = `n/a` + "No GPU reported by this agent", uncoloured; **no** GPU chart and no hole where one would be | `system-gateway.png` (dark), `system-gateway-light.png` |
|
||||
|
||||
Full-page shapes: `system-shapes-dark.png`, `system-shapes-light.png`.
|
||||
|
||||
Failure path: stopped only the gateway tunnel (same commands `dev-tunnels.sh stop` runs,
|
||||
scoped to the 9123 entry; remote agent stopped too). After 15 poll failures the panel kept
|
||||
its last-good heroes (MEM 32 %, DISK 61 %, NET rates — nothing zeroed, nothing `n/a`), its
|
||||
dot turned red, the header read `stale 2.1m`, and the card dimmed via `.stale`. The other
|
||||
two panels stayed green ("one dead source must not blink another"). Store recorded exactly
|
||||
one `unreachable` and, after `./scripts/dev-tunnels.sh start`, one `reachable` event.
|
||||
Screenshot: `system-stale-gateway.png` (+ `system-stale-overview.png` full page). Restored;
|
||||
`dev-tunnels.sh status` and `/api/health` back to all-OK.
|
||||
|
||||
Accessibility pass:
|
||||
|
||||
- hero values are real selectable text (`div.value > span.num/span.unit`, no SVG paths) —
|
||||
asserted in-page for every hero on all three panels;
|
||||
- colour is never the only signal: each hero prints the number, the NET slot is
|
||||
deliberately colourless, staleness reads as text (`updated Ns` / `stale 2.1m`);
|
||||
- charts — **defect found**: the SVGs were anonymous (`aria-label` absent on container and
|
||||
svg). Fixed: `LineChart` gained an `ariaLabel` option that sets `role="img"` +
|
||||
`aria-label` on the `<svg>`, and every `/system` chart now names its host and metrics
|
||||
(e.g. "driveripper — utilisation over the selected window: CPU, memory and worst disk in
|
||||
per cent"). Pinned by `tests/test_api.py::test_every_system_chart_names_itself_for_assistive_tech`.
|
||||
The `/llm` and `/ups` charts can adopt the same option in phases 04/05.
|
||||
|
||||
Both themes checked via the header toggle (severity colours come from theme tokens; the
|
||||
light-theme deskwork fail-red and warn-amber read clearly — see
|
||||
`system-shapes-light.png`, `system-gateway-light.png`). Note for the next shooter: set
|
||||
`prefers-color-scheme` or seed `localStorage['dash-theme']`; Playwright defaults to light,
|
||||
which silently mislabels a "dark" screenshot.
|
||||
|
||||
`curl "localhost:8090/api/system/history?hosts=driveripper&metrics=cpu.usage_pct@core7&range=1h"`
|
||||
→ 354 points: dropping the per-core plot did not drop the data.
|
||||
|
||||
## Defects found and fixed during verification
|
||||
|
||||
1. **Unlabelled charts** (above) — fixed in `charts.js` + `page-system.js`, test-pinned,
|
||||
re-screenshotted.
|
||||
2. **`/system` Events panel was permanently empty** — the poller stores transition events
|
||||
under the *dashboard* name (`system`) while the page queried `/api/events?source=host`,
|
||||
so the feed said "No host transitions recorded yet" even with the deliberate gateway
|
||||
outage in the store. Fixed to `source=system` (with a comment on the naming trap);
|
||||
`tests/test_api.py::test_system_event_feed_queries_the_source_transitions_are_stored_under`
|
||||
pins it; the re-shot full-page screenshots show the feed populated with the outage and
|
||||
recovery. Pre-existing since the first commit, not introduced by phase 03.
|
||||
|
||||
## After the fixes
|
||||
|
||||
`bash .agents/validate.sh` → exit 0: 460 passed + 7 skipped, coverage 96.86 % (≥ 94),
|
||||
ruff + pyright clean, `node --check` clean. `uv run pytest -m live --live` → 7 passed.
|
||||
Console stayed error-free throughout.
|
||||
Reference in New Issue
Block a user