phase: 01_severity_language

**Gate defect found & fixed (the "does not lint" bug).** `.agents/validate.sh` — the phase gate — ran `python3 -m pytest` on `/usr/bin/python3` + `~/.local` site-packages (not the uv venv), ran `ruff check .` *only if* a ruff happened to be on PATH, and **never ran pyright or the coverage floor**. Rewrote it to the project's real gate: cd to repo root → `uv run pytest --cov --cov-fail-under=94` → `uv run ruff check src tests agent` → `uv run pyright` → `node --check web/static/*.js` → hygiene gates; a missing toolchain now fails loudly. Provoked it to confirm it bites (floor + a seeded F401 both fail).

**Verification (all green)**
- `bash .agents/validate.sh` → **exit 0**: 356 passed, 6 skipped · coverage **94.75%** (floor 94) · ruff `All checks passed!` · pyright `0 errors` · node syntax clean
- `uv run pytest --cov` → `severity.py` **100%**; `uv run pytest -m live --live` → **6 passed**; `uv pip list --outdated` → empty
- Criteria: `classify('cpu.usage_pct',…)` → `fail`/`ok`/`None` ✓ · `/api/ups/snapshot` carries `severity` beside `charge_pct`/`load_pct`/`battery_runtime` (apc now live-`warn` at 498 s — threshold working) ✓ · no `.sev-*`/synonym vocabulary (only a comment saying so) ✓ · pytest/coverage/lint ✓ · dev server restarted, `/api/health` ok 6/6 sources, all pages+assets 200 ✓
- Chromium against live fleet: `severityFrom` cell→`fail`, bare→`warn`, null→`''`, unrated→`''`; `hero` fail → `rgb(207,34,46)`, null → `n/a` with **no** verdict class, warn+bar 42%; all four pages render unchanged → `data/logs/phase01-final-{index,system,llm,ups}.png`

**Docs (full review, incl. pre-existing drift).** README: `/ups` row claimed "upsd on 3493" (fleet is 3051, stated elsewhere in the same file); config table omitted `DASH_HOST/PORT`, llm/ups `*_INTERVAL_SECONDS`, `*_TIMEOUT_SECONDS`, `GUFO/VLLM_API_KEY`, `POLL_MS_*`, `DEBUGPY` (+ dedup); severity bullet omitted `disks`/`gpus[]`/rated LLM cells and the ratio rescale; agent section listed only `/snapshot` though `/info`+`/health` exist. AGENTS.md: same 3493 claim; `install-agent.sh --token-file` (its flags are `--bind/--port/--token/--rotate-token/--status/--uninstall` — it hands *the agent* `--token-file`); gap 3 "Two commits" (now 6, still no `origin`); gap 1 given the sudoers install command + verified live gating reason; coverage line now 80% repo floor vs 94% gate floor; cyberpower `identity.model: GX1500U` vs `ups.description` quirk. `.env.dev.example`: `DEV_FORWARDS` remote port 3493→3051 and engine URLs that pointed at `127.0.0.1` with nothing forwarding them.

**Deviations.** No `ruff format` (30 files would reformat; the project gate is `ruff check` + pyright — reformatting completed phases is out of scope). **No live-infra changes**: enabling the battery-test buttons needs `deploy/nut-sudoers` installed on driveripper + a nut-server restart and a scoped `[dashboards]` `instcmds` user — prod write with a documented shutdown risk (AGENTS gap 1), so repo-side only. 36 pre-existing `ResourceWarning: unclosed database` from test fixtures are noise, not failures; left rather than churn ~15 fixtures. `severity.attach()` is unit-tested but has no production call site until phases 02–05.

**Next pending phase:** `02_overview_fleet_and_alerts` (`01_alerts_builder.md`).
This commit is contained in:
2026-10-16 22:22:26 -04:00
parent 4b4962d7fb
commit ad993c7b03
40 changed files with 1384 additions and 25 deletions
@@ -0,0 +1,13 @@
**Gate defect found & fixed (the "does not lint" bug).** `.agents/validate.sh` — the phase gate — ran `python3 -m pytest` on `/usr/bin/python3` + `~/.local` site-packages (not the uv venv), ran `ruff check .` *only if* a ruff happened to be on PATH, and **never ran pyright or the coverage floor**. Rewrote it to the project's real gate: cd to repo root → `uv run pytest --cov --cov-fail-under=94` → `uv run ruff check src tests agent` → `uv run pyright` → `node --check web/static/*.js` → hygiene gates; a missing toolchain now fails loudly. Provoked it to confirm it bites (floor + a seeded F401 both fail).
**Verification (all green)**
- `bash .agents/validate.sh` → **exit 0**: 356 passed, 6 skipped · coverage **94.75%** (floor 94) · ruff `All checks passed!` · pyright `0 errors` · node syntax clean
- `uv run pytest --cov` → `severity.py` **100%**; `uv run pytest -m live --live` → **6 passed**; `uv pip list --outdated` → empty
- Criteria: `classify('cpu.usage_pct',…)` → `fail`/`ok`/`None` ✓ · `/api/ups/snapshot` carries `severity` beside `charge_pct`/`load_pct`/`battery_runtime` (apc now live-`warn` at 498 s — threshold working) ✓ · no `.sev-*`/synonym vocabulary (only a comment saying so) ✓ · pytest/coverage/lint ✓ · dev server restarted, `/api/health` ok 6/6 sources, all pages+assets 200 ✓
- Chromium against live fleet: `severityFrom` cell→`fail`, bare→`warn`, null→`''`, unrated→`''`; `hero` fail → `rgb(207,34,46)`, null → `n/a` with **no** verdict class, warn+bar 42%; all four pages render unchanged → `data/logs/phase01-final-{index,system,llm,ups}.png`
**Docs (full review, incl. pre-existing drift).** README: `/ups` row claimed "upsd on 3493" (fleet is 3051, stated elsewhere in the same file); config table omitted `DASH_HOST/PORT`, llm/ups `*_INTERVAL_SECONDS`, `*_TIMEOUT_SECONDS`, `GUFO/VLLM_API_KEY`, `POLL_MS_*`, `DEBUGPY` (+ dedup); severity bullet omitted `disks`/`gpus[]`/rated LLM cells and the ratio rescale; agent section listed only `/snapshot` though `/info`+`/health` exist. AGENTS.md: same 3493 claim; `install-agent.sh --token-file` (its flags are `--bind/--port/--token/--rotate-token/--status/--uninstall` — it hands *the agent* `--token-file`); gap 3 "Two commits" (now 6, still no `origin`); gap 1 given the sudoers install command + verified live gating reason; coverage line now 80% repo floor vs 94% gate floor; cyberpower `identity.model: GX1500U` vs `ups.description` quirk. `.env.dev.example`: `DEV_FORWARDS` remote port 3493→3051 and engine URLs that pointed at `127.0.0.1` with nothing forwarding them.
**Deviations.** No `ruff format` (30 files would reformat; the project gate is `ruff check` + pyright — reformatting completed phases is out of scope). **No live-infra changes**: enabling the battery-test buttons needs `deploy/nut-sudoers` installed on driveripper + a nut-server restart and a scoped `[dashboards]` `instcmds` user — prod write with a documented shutdown risk (AGENTS gap 1), so repo-side only. 36 pre-existing `ResourceWarning: unclosed database` from test fixtures are noise, not failures; left rather than churn ~15 fixtures. `severity.attach()` is unit-tested but has no production call site until phases 02–05.
**Next pending phase:** `02_overview_fleet_and_alerts` (`01_alerts_builder.md`).
@@ -0,0 +1,217 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 19%]
.....................................................................sss [ 39%]
sss..................................................................... [ 59%]
........................................................................ [ 79%]
........................................................................ [ 99%]
../usr/lib64/python3.14/collections/__init__.py:453: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59ca0487c0>
@classmethod
ResourceWarning: Enable tracemalloc to get the object allocation traceback
/usr/lib64/python3.14/collections/__init__.py:455: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c9817a60>
result = tuple_new(cls, iterable)
ResourceWarning: Enable tracemalloc to get the object allocation traceback
[100%]/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/_pytest/unraisableexception.py:33: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c9817010>
gc.collect()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/_pytest/unraisableexception.py:33: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59ca19db70>
gc.collect()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/_pytest/unraisableexception.py:33: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59ca19cf40>
gc.collect()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/_pytest/unraisableexception.py:33: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c9797f10>
gc.collect()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
tests/test_api.py::test_unconfigured_source_is_404_configured_false[/api/ups/snapshot]
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/telemetry/_runtime.py:213: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cbae7f10>
async def wrapped_receive() -> Message:
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_api.py::test_unconfigured_source_is_404_configured_false[/api/ups/snapshot]
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/telemetry/_runtime.py:213: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cbaf89a0>
async def wrapped_receive() -> Message:
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_api.py::test_unconfigured_source_is_404_configured_false[/api/ups/snapshot]
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/telemetry/_runtime.py:213: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cbc43a60>
async def wrapped_receive() -> Message:
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_api.py::test_summary_still_answers_with_no_sources
/usr/lib64/python3.14/urllib/request.py:1888: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59ca19e5c0>
if len(name) > 5 and name[-6] == "_" and name[-5:].lower() == "proxy":
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_api.py::test_agent_tokens_never_reach_the_browser
/var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/utils.py:43: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59ca19e2f0>
def get_path_param_names(path: str) -> set[str]:
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cb16a2f0>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cb1a7b50>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cb1a7d30>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cb0671f0>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cb0d3c40>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59caf37880>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cae1be20>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cae1bf10>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cae9bc40>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59caefe200>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cad53b50>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cadef2e0>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cac6bb50>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cac6b970>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59cab4b790>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c8d97790>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c8d976a0>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c848b4c0>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c832aa70>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c8397970>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c82535b0>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c82c62f0>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c81fb010>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c809bc40>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
tests/test_main.py::test_main_serves_the_assembled_app
<string>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f59c8163790>
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.14.4-final-0 ________________
Name Stmts Miss Cover Missing
------------------------------------------------------------------
src/dashboards/__init__.py 3 0 100%
src/dashboards/__main__.py 14 1 93% 28
src/dashboards/api.py 190 28 85% 59-60, 62, 280-286, 303-311, 318-322, 406, 411-412, 418-429
src/dashboards/app.py 109 7 94% 50-51, 56, 58, 60, 97, 131
src/dashboards/collectors.py 140 9 94% 78-79, 92-93, 97-98, 115, 232, 236
src/dashboards/config.py 110 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 139 1 99% 226
src/dashboards/prom.py 105 5 95% 107, 144, 158, 161, 164
src/dashboards/rates.py 37 1 97% 51
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/host.py 199 2 99% 122, 147
src/dashboards/sources/llm.py 150 1 99% 226
src/dashboards/sources/nut.py 367 22 94% 118, 132, 138, 150, 156, 168, 176, 181, 252-253, 255, 414, 458, 470, 505, 538, 542, 595, 599-600, 605, 617
src/dashboards/sources/nut_sim.py 120 21 82% 129, 155-173, 214
src/dashboards/store.py 178 3 98% 204, 243-244
------------------------------------------------------------------
TOTAL 1924 101 95%
Required test coverage of 94% reached. Total coverage: 94.75%
356 passed, 6 skipped, 31 warnings in 39.85s
== uv run ruff check src tests agent ==
All checks passed!
== uv run pyright ==
0 errors, 0 warnings, 0 informations
== node --check src/dashboards/web/static/*.js ==
@@ -0,0 +1,17 @@
**Implemented**
- `src/dashboards/severity.py` (new leaf module): `THRESHOLDS` (the 10 ratified rows), `INVERTED` (`ups.charge_pct`, `ups.battery_runtime`), `classify()` → `ok|warn|fail|None`, `attach()` for bare numbers, `with_severity()` for `{value, source, derived}` cells; docstring states "unknown is not ok" + "a threshold exists nowhere else" and that `_pct` metrics are 0-100.
- `tests/test_severity.py` (new, 64 tests): every metric walked across both boundaries, warn/danger edges inclusive, both directions for inverted metrics, table-sanity test (bound ordering per direction, `INVERTED ⊆ THRESHOLDS`), pinned ratified table, unknown metric / `None` / NaN / ±inf → `None`, `@stream` inheritance (`disk.used_pct@/srv`, `gpu.busy_pct@0`), attach/with_severity shapes incl. no caller-mutation and JSON-safety.
- No page, API, CSS, or payload code touched — severity is not consumed yet. No live infra touched.
**Verification**
- `uv run pytest tests/test_severity.py -v` → 64 passed
- `uv run pytest` → **322 passed, 6 skipped** (baseline 258+6, no regressions); `uv run pytest --cov` → `severity.py` **100%**, total **94%**
- `uv run ruff check src tests agent` → All checks passed; `uv run pyright` → 0 errors
- `uv run python -c "from dashboards.severity import classify as c; print(c('cpu.usage_pct',95), c('cpu.usage_pct',60), c('cpu.usage_pct',None), c('ups.charge_pct',15))"` → `fail ok None fail`
- Deps unchanged → `uv pip list --outdated` not applicable.
**Docs**: `AGENTS.md` — one `severity.py` line added to the module-responsibility list. No README impact (no new setup/command/API surface yet).
**Decisions**: reaching a threshold trips it in both directions (`cpu.usage_pct` 75 → warn, 90 → fail; `ups.charge_pct` 50 → warn, 20 → fail); `metric@stream` classifies with its base metric's row; `attach()` normalises non-finite values to `None` so payloads stay valid JSON, `with_severity()` is strictly additive (a missing cell gains only `severity`). No screenshot taken — this task changes no UI; the phase's render-unchanged screenshot belongs with task 03.
**Next pending task**: `.agents/phases/todo/01_severity_language/02_severity_in_payloads.md`
@@ -0,0 +1,16 @@
........................................................................ [ 21%]
.......................................................ssssss........... [ 43%]
........................................................................ [ 65%]
........................................................................ [ 87%]
........................................ [100%]
=============================== warnings summary ===============================
../../../.local/lib/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/.local/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
All checks passed!
repo hygiene: TRACKED files look like log drops / key material (untrack with 'git rm --cached <file>' and add a .gitignore rule):
.env.dev.example
.env.example
validation FAILED (see output above)
@@ -0,0 +1,17 @@
**Fixed (rejection cause)**
- `validate.sh` gate #2 flagged the repo's tracked env templates `.env.example` / `.env.dev.example` — they are tracked **by design** (`.gitignore` ignores `.env.*` and un-ignores exactly those two so a clone has documented setup; `cp .env.example .env` is in README/AGENTS.md). Untracking them would break the documented workflow, so I adapted the gate instead (its header says to adapt it per project):
- narrowly allow-list `(^|/)\.env[^/]*\.(example|template|sample)$` — real `.env`, `.env.dev`, `*.pem/key/crt`, `*.log`, `*.jsonl` still fail (verified against a synthetic file list).
- added gate **2b** so the allow-list can't smuggle a secret: every tracked `*.example` template's `*_PASSWORD|*_TOKEN|*_SECRET|*_KEY` assignment must be empty or a placeholder word. Passes on both templates (`replace-me-on-the-host`, `SECRET`, empty), fails on a planted real-looking value.
- No test/lint/code defects found in this or prior work; `severity.py` + tests unchanged from the accepted implementation.
**Verification**
- `./.agents/validate.sh` → **rc=0** (pytest 322 passed / 6 skipped; `ruff check .` All checks passed; hygiene gates silent)
- `uv run pytest` → **322 passed, 6 skipped**; `uv run pytest --cov` → `severity.py` **100%**, total **94%**
- `uv run ruff check src tests agent` → All checks passed; `uv run pyright` → 0 errors
- `classify` criteria one-liner → `fail ok None fail`
**Docs**: `AGENTS.md` — `severity.py` line in the module-responsibility list (from the prior pass, still accurate). Gate adaptation is documented inline in `.agents/validate.sh`; no README change needed (setup steps unchanged).
**Notable decisions**: kept the templates tracked rather than untracking, because the hygiene gate's intent (no key material / log drops) is preserved and strengthened by gate 2b; no `git rm --cached` used (not sanctioned by this task file).
**Next pending task**: `.agents/phases/todo/01_severity_language/02_severity_in_payloads.md`
@@ -0,0 +1,12 @@
........................................................................ [ 21%]
.......................................................ssssss........... [ 43%]
........................................................................ [ 65%]
........................................................................ [ 87%]
........................................ [100%]
=============================== warnings summary ===============================
../../../.local/lib/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/.local/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
All checks passed!
@@ -0,0 +1,13 @@
**Task 02 — Severity in the payloads: done**
- `sources/host.py`: added `severity` sub-dicts — `cpu.severity{usage_pct,temp_c}`, `memory.severity{used_pct}`, `disks.severity{worst_used_pct}`, `gpus[].severity{busy_pct,mem_used_pct}`; all existing keys untouched.
- `sources/nut.py`: each UPS card now has `severity{charge_pct,load_pct,battery_runtime}`; `health`/`checks[].status`/`numbers` unchanged.
- `sources/llm.py`: `attach_cell_severity()` puts `severity` inside the KV cells (`kv_cache_usage_ratio`, `gpu_cache_usage`) via `severity.with_severity`; `value/source/derived` intact.
- `api.py` `/api/summary`: `severity` per host row, per UPS row, and on the fleet aggregate (`/api/health`, `/api/config` untouched).
- `severity.with_severity` gained an optional `scale` (ratio→percent, **verdict only**) so 0.9 KV pressure is `fail`, not a permanent green; task-01 tests unchanged.
- Regression caught and fixed by the new tests: a local-name collision in `parse_host_snapshot` had overwritten `memory.used_pct` with the GPU pool %; test asserts `memory.used_pct == used/total`.
- Tests: `uv run pytest --cov` → **349 passed, 6 skipped, TOTAL 95%** (severity.py 100%, api.py/host.py/nut.py/llm.py gaps all pre-existing route bodies); `ruff check src tests agent` clean; `pyright` 0 errors; `uv pip list --outdated` empty.
- Live: `uv run pytest -m live --live` → **6 passed**; dev server restarted, curl confirms severity on `/api/system/snapshot` (gateway `temp_c: null`), `/api/ups/snapshot` (apc + cyberpower ok; `battery_temperature` null with health check still `unknown`), `/api/summary`, `/api/llm/snapshot`.
- Docs: AGENTS.md severity bullet now states the additive payload contract; test count 258→349; README architecture gained a `severity.py` bullet.
- Note: `deploy/nut-sudoers` was modified in the working tree by something outside this task (grant line, mtime mid-session) — I left it untouched for the harness to judge.
- Next pending task: `01_severity_language/03_hero_number_style.md`.
@@ -0,0 +1,12 @@
........................................................................ [ 20%]
..............................................................ssssss.... [ 40%]
........................................................................ [ 60%]
........................................................................ [ 81%]
................................................................... [100%]
=============================== warnings summary ===============================
../../../.local/lib/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/.local/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
All checks passed!
@@ -0,0 +1,10 @@
**Task 03 — Hero-number style and the JS side of severity: complete**
- `dash.css`: new `hero numbers` block (`.hero`, `.hero .label/.value/.unit/.sub`, `.hero.ok|.warn|.fail .value`, `.hero-bar` + `.hero-bar.ok .bar > i`) built only from existing tokens; 2.4rem tabular-numeral value, faint `n/a` placed after the verdict rules so an unmeasured number is never coloured.
- `app.js`: `Dash.hero(el,{label,value,unit,digits,severity,sub,bar})` renders/patches a hero in place (`n/a` + **no** severity class when the value is null/non-finite, colour class dropped on the transition); `Dash.severityFrom(cell|string|null)` → `ok|warn|fail|''`; `severityFor` kept, documented as the fallback with "do not add a call site"; both new helpers exported. Pages untouched (phases 02–05 convert them).
- Tests (7 new, `tests/test_api.py`): served CSS has every hero rule + the shared verdict tokens; no `.sev-*`/synonym classes in any asset or page (comments stripped first); `hero()` body must call `severityFrom` and must not contain `severityFor`; no stylesheet rule uses an undeclared `var(--token)`; `severityFor` still exported for the four pages.
- Live browser verification (real Chromium against `:8090`, live fleet/engines/upsd): `severityFrom` cell/bare/null/junk → `fail`/`warn`/`''`/`''`; hero classes `hero ok|hero warn|hero fail|hero`, fail computed colour `rgb(207,34,46)` at 38.4px (2.4rem), bar width+class from the verdict, null value = `n/a` with `is-null`; all four pages render unchanged. Screenshots: `data/logs/hero-number-preview.png`, `hero-task-{index,system,llm,ups}.png`.
- Green: `uv run pytest` → **356 passed, 6 skipped**; `uv run pytest --cov` → TOTAL **95%**, `severity.py` 100%; `uv run ruff check src tests agent` → All checks passed; `uv run pyright` → 0 errors; `uv run pytest -m live --live` → **6 passed**; `uv pip list --outdated` empty; `curl -s localhost:8090/static/dash.css | grep -c "\.hero"` → 17.
- Docs: AGENTS.md — `web/` responsibility names `Dash.hero`/`Dash.severityFrom`, UI conventions describe `.hero`/`.hero-bar` and the no-`.sev-*` rule, counts refreshed (356 tests, ~95%); README — `web/` bullet documents the hero helper and "no verdict → `n/a`, no colour".
- Decision: `hero()`'s optional `bar` colours itself only from the payload verdict (never `severityFor`), and `.hero-bar.ok .bar > i` lets hand-written markup inherit the verdict from the hero, so no component can reintroduce a JS threshold.
- Next pending task: `02_overview_fleet_and_alerts/01_alerts_builder.md` (phase 01 tasks all done).
@@ -0,0 +1,13 @@
........................................................................ [ 19%]
.....................................................................sss [ 39%]
sss..................................................................... [ 59%]
........................................................................ [ 79%]
........................................................................ [ 99%]
.. [100%]
=============================== warnings summary ===============================
../../../.local/lib/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/.local/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
All checks passed!