phase: 09_disks_smartctl

**Phase 09 — final verification pass (all 4 task files already in `complete/`; nothing re-implemented)**

- Verified `GET /api/disks/snapshot` live: 3 hosts / 18 whole-`/sys/block` rows, `smartctl_available` three-way, per-row `severity`, API-summed `fleet`; 404 `{"configured": false}` proven *without dialling* (spies on `httpx.AsyncClient.get` + `collectors.fetch_snapshot`).
- Verified `/api/disks/history` live (`disk.temp_c@dev`, unknown names → empty series) + `test_store.py`'s 300 s rollup/prune buckets; `/api/metrics?source=disks` answers the `disks` family.
- Browser against live data: 18 drive cards (never a partition/mount), collapsed per-host `Charts` with `aria-label`, 3 `.stale` grids + one `.banner.info` why-line and **0 `.fail` classes / green `disks` dot** → criteria 3 and 4 hold; new shots `data/logs/09-verify-disks-live-dimmed.png`, `09-verify-overview-live.png` (13 total for the phase).
- Real-hardware criterion: read 4 driveripper drives through `ops`' `sudo -n smartctl -i -A -H` and ran the live output through `host_agent.parse_smart_output` → `sources/disks.parse_disks_snapshot`: `sda` 35 °C / 30 423 h, `nvme1n1` 58 % wear, `nvme5n1` 13 uncorrectable → `alarm_disks: 1`, temps graded `warn`; consistent with the 16 committed fixtures.
- `bash .agents/validate.sh` → **EXIT=0**; `uv run pytest --cov` → **769 passed, 11 skipped, coverage 98.33%** (floor 94); `ruff check src tests agent` clean; `pyright` 0 errors; `node --check` clean; hygiene + dependency advisories clean. `uv run pytest -m live --live` → 10 passed, 1 skipped (agents still predate the SMART pass). Dev server healthy: `/api/health` `ok`, 9/9 sources.
- Docs fixed: AGENTS.md test count 698→769 and coverage 98.2→98.33 %; `disks` added to the event dashboard-name list and to the "every feed renders `Dash.eventText`" sentence; README `POLL_MS_*` row now states `/disks`' deliberate fixed 30 s; new Known gap 4 records the dev-DB `database is locked` writes (reported/recovered per invariant 4, ~590 MB DB / ~70 MB WAL, not reproduced by a controlled 25 s external read txn).
- Deviations: **no live-infra change** — granting the agent user the `disk` group and redeploying `host_agent.py` on driveripper/deskwork stay the documented paired operator action (redeploying without the grant yields 16 permission-denied lines; a sudo-spawning agent would break the confirmed "direct read, no sudo" assumption). Store-lock issue left to its own phase rather than patched speculatively.
- Next pending phase: `10_dev_direct_dial`.
This commit is contained in:
2026-10-18 00:45:01 -04:00
parent 19cfedfddf
commit fefbeb8227
81 changed files with 5525 additions and 134 deletions
+42
View File
@@ -22,6 +22,8 @@ RATIFIED = {
"cpu.usage_pct": (75.0, 90.0),
"mem.used_pct": (80.0, 92.0),
"disk.used_pct": (80.0, 90.0),
"disk.temp_c": (50.0, 70.0),
"disk.percentage_used": (80.0, 100.0),
"cpu.temp_c": (75.0, 90.0),
"llm.kv_cache_usage_pct": (75.0, 90.0),
"ups.charge_pct": (50.0, 20.0),
@@ -134,10 +136,50 @@ def test_non_finite_values_are_treated_as_missing(metric: str, value: float) ->
assert classify(metric, value) is None
def test_a_drive_temperature_and_its_wear_are_the_only_disk_verdicts() -> None:
"""Phase 09: the two `/disks` numbers worth a colour, with the bounds the phase set.
A spinning drive at 70 °C is a fire and an SSD that has consumed its rated wear is a
replacement, so both trip red; the amber in between is the "watch it" band. An SSD with
no temperature sensor has **no value**, and therefore no verdict — the absence of a
number is not a healthy reading (`classify(metric, None)` stays `None`).
"""
assert classify("disk.temp_c", 49.9) == OK
assert classify("disk.temp_c", 50) == WARN
assert classify("disk.temp_c", 69.9) == WARN
assert classify("disk.temp_c", 70) == FAIL
assert classify("disk.temp_c", 75) == FAIL
assert classify("disk.temp_c", None) is None
assert classify("disk.percentage_used", 79.9) == OK
assert classify("disk.percentage_used", 80) == WARN
assert classify("disk.percentage_used", 100) == FAIL
assert classify("disk.percentage_used", None) is None
def test_sector_counters_and_power_on_hours_are_never_graded() -> None:
"""A non-zero sector count is an alarm, not a threshold breach (`/llm`'s chip rule).
Colouring 0 reallocated sectors green would call a drive healthy because a number is
small, and colouring 1 amber would make the chip redundant: the page shows an alarm on
**presence** and says nothing at all at zero. `power_on_hours` is an odometer with no bad
value. So none of them may ever grow a row in `THRESHOLDS`.
"""
counters = ("disk.reallocated", "disk.pending", "disk.uncorrectable",
"disk.power_on_hours", "disk.health_ok")
assert set(counters).isdisjoint(THRESHOLDS), "a counter is not a graded number"
for metric in counters:
assert classify(metric, 0) is None
assert classify(metric, 1) is None
assert classify(metric, 99_000) is None
assert classify(f"{metric}@sda", 1) is None
def test_per_device_streams_inherit_their_base_metric() -> None:
assert classify("disk.used_pct@/srv", 95) == "fail"
assert classify("disk.used_pct@/srv", 10) == "ok"
assert classify("cpu.usage_pct@core7", 80) == "warn"
assert classify("disk.temp_c@nvme0n1", 71) == "fail"
assert classify("disk.percentage_used@nvme0n1", 80) == "warn"
assert classify("mem.used_pct@any", 95) == "fail"