chore(agent): phase roadmap from TODO.md, 4 phases

06 system_page_gpu_verdicts — GPU stops carrying a verdict, GPU temp on the
   hero row, per-host history charts collapsed behind a toggle (TODO L3/L4/L9)
07 llm_monitor_style_pages — /slots schema pinned from the live server,
   broken slot table removed, fleet totals block, stats strip, slot grid
   (full restyle; TODO L5)
08 events_paging_and_copy — event feeds scrollable + paginated, flavour text
   stripped, rule pinned in AGENTS.md (TODO L6/L7)
09 disks_smartctl — smartctl physical-disk collection in the host agent,
   5-minute disks source/poller/store, /api/disks + severity rows, /disks
   page + nav tab + overview entry (TODO L8)

LLM-Generated: true
This commit is contained in:
2026-10-17 11:39:29 -04:00
parent 34854d32af
commit 2460a0adf5
17 changed files with 935 additions and 0 deletions
@@ -0,0 +1,52 @@
# Phase 09 — A /disks page for physical disk health (smartctl)
**Source:** `TODO.md` L8 — "a truly physical disk monitoring page": "Add a 'disks' tab dedicated to smartctl and per-disk metrics. Note: not per-partition, but truly a physical disk monitoring page that collects data about all my physical disks across my fleet (should only be deskwork and driveripper right now)."
**Story:** n/a
**Context:** `agent/host_agent.py` (physical whole disks via `/sys/block`, ~L400 — already partition-free; stdlib-only, `ProtectSystem=strict`), `src/dashboards/config.py` (env only, sources default to unset), `src/dashboards/poller.py` (one task per source, stale/last-good, events under the dashboard name), `src/dashboards/store.py` (WAL, rollups raw→1m→1h, retention — rule 9), `src/dashboards/api.py` (GETs, 404-when-unconfigured without dialling), the three existing dashboards + `/` overview (a new tab joins the nav and the overview's "which of the three" table)
## Objective
A fourth dashboard: `/disks` — one card per **physical** disk (the agent's whole-
`/sys/block` enumeration, never partitions), each carrying `smartctl` health: overall
health, temperature, power-on hours, and the sector/wear alarms. Its own `disks` source
and poller task at a slow 5-minute cadence (SMART is not a per-second signal), its own
API (`/api/disks/snapshot`, `/api/disks/history`), rollups and retention, and a page that
reads `n/a` for what `smartctl` cannot see — never red for a missing binary.
- ASSUMPTION (phase, confirmed): the agent reads SMART **directly** (`smartctl` as the
agent user, e.g. `disk` group) — no sudo — and `DISKS_INTERVAL_SECONDS` defaults to
**300** s. `smartctl` absent or a disk unreadable → that disk's `smart` is `null`
with an error entry; the page says "smartctl not available" dimmed (invariants 3/4:
unconfigured is not broken; a dead source must not blink another).
## Dependencies
- `08_events_paging_and_copy` (todo) — sequence only: it settles `app.js`/`dash.css`
(the pager + copy audit) that the new page builds on; no hard code dependency.
## Tasks
1. `01_agent_smartctl_collection.md` — `host_agent.py` gains a `smartctl` pass over its existing whole-disk list (per-device `smartctl -i -A -H`, stdlib `subprocess`, timeout-bounded); fixture from the real disks; deploy docs.
2. `02_disks_source_poller_store.md` — the `disks` source (payload + `disk.*@dev` samples), its own poller job at `DISKS_INTERVAL_SECONDS` (default 300), rollups + retention in `store.py`.
3. `03_disks_api_and_config.md` — `/api/disks/snapshot` + `/api/disks/history`, config wiring, `404 {"configured": false}` without dialling, severity rows for the health/temperature metrics.
4. `04_disks_page_and_overview.md` — the `/disks` page (per-disk cards, alarm chips, per-host history charts), nav tab + overview entry + header status dot.
## Testing & Quality
- Hermetic: fixture-driven agent tests (`tests/test_agent.py` + recorded `smartctl`
output), `tests/test_disks_source.py` (new), `tests/test_api.py` key-path pins (with
and without config), `node --check`.
- Live: `uv run pytest -m live --live` — the agent on deskwork/driveripper answers
`smart` fields for its real disks; **live tests must not write** (no `smartctl -t`, no
self-test on hardware something is plugged into).
- Browser (DoD): screenshots in `data/logs/` — per-disk cards on live data, the
"smartctl not available" state, the overview with the new tab.
- Coverage: **>90%** on new/modified code, ≥94% overall gate maintained.
## Completion Criteria
- [ ] `GET /api/disks/snapshot` answers per-disk `smart` data for every physical disk the agents report; `404 {"configured": false}` with no agents configured, without dialling
- [ ] `/api/disks/history` serves the `disk.*@dev` series through the existing rollups
- [ ] the `/disks` page renders one card per physical disk (never per-partition) with health, temp, power-on hours and non-zero alarm chips
- [ ] a host without `smartctl` (or a denied read) shows dimmed `n/a` cards, never a red page
- [ ] `bash .agents/validate.sh` green; screenshots in `data/logs/`