phase: 15_agent_mtls

All green. Report:

**Verified / implemented (final pass of `15_agent_mtls`)**
- Wrote `data/logs/verify-15-tls-e2e.py`: a real `host_agent.py --cert-bundle` + a real dashboard process over a scratch DB (`HOST_TARGETS=loop=https://…`) — **27/27 PASS**, transcript `verify-15-tls-e2e.out`, notes `data/logs/NOTES-tls-rollout.md`.
- Read-only checks against the running dev store: 5 `certs.*` rows byte-identical across a restart; served bundle == the `meta` row (1430 B); 0 events containing `-----BEGIN`; unsigned `/certs`→`303 ?next=`, unsigned `/api/certs`→`401`, signed-in `200`; screenshots `data/logs/15-final-{certs,system}.png`.
- Docs (step 6, full review): README — added *Protecting an agent, host by host* (the rollout task-07 left out), a `tests/test_certs.py`/TLS-matrix/sandbox-installer paragraph, and fixed two stale “the one write endpoint in the app” claims (`NUT_ALLOW_ACTIONS`, Battery-test buttons). AGENTS.md — test count 1424→1427, bundle-fetch command in *Commands*, a Testing-policy bullet on structurally pinning a key-holding module, fleet transport bullet (token mode is now the compatibility path), gap 11 final state.

**Tests / lint (full suite 100% green)**
- `uv run pytest` → **1427 passed, 15 skipped**, exit 0 · `bash .agents/validate.sh` → **exit 0** (coverage **98.94%** vs 94% floor, `certs.py` 100%; ruff “All checks passed”; pyright 0 errors; `node --check` clean; hygiene clean)
- `uv run pytest -m live --live` → **11 passed / 0 failed** · `./scripts/dev-server.sh status` → all 9 sources `ok`
- `uv pip list --outdated` → empty after bumping `fastapi 0.152.0 → 0.153.0` (`uv.lock` only); full suite re-run green.

**Completion criteria**
- Fresh-DB root CA, idempotent: ✅ (tests + live store hashes) · agent TLS matrix incl. wrong-CA/certless 403/`/health` CA-only/revoked 403 + live denylist re-read: ✅
- `https` `host:`/`disks:` `ok` + revoked says “agent certificate revoked” with no dial: ✅ on loopback (real agent + real dashboard) and spy-pinned hermetically; **not** on the fleet (below)
- Signed-in `/certs` + rotate/revoke/root-rotate + byte-identical bundle download: ✅ · installer bundle/unit/`--status` over TLS: ✅ (0640, see deviation)
- `pytest`/`validate.sh`/`--outdated`: ✅ · token-mode byte-identical, engine/UPS untouched, public surface unchanged (route walk): ✅
- **Live fleet TLS (deskwork+gateway flip): NOT done** — no live changes. It needs root on hosts without the installer grant (`deskwork`), has no both-ways-working window (`http://` dashboard vs https-only agent), and would deploy the agent from this not-yet-committed tree. Exact procedure recorded in README + `data/logs/NOTES-tls-rollout.md`.

**Deviations**: bundle mode `0640 root:dashboards-agent` instead of the task file’s `0600` (the agent re-reads its own bundle for the denylist; 0600 breaks the unit — already recorded in AGENTS.md); the loopback dashboard substitutes for the fleet flip. Incidental: a stray `pkill` of mine killed the dev server mid-pass; restarted and verified healthy.

**Next pending phase**: `.agents/phases/todo/16_agent_smart_pass_budget` (then `17_installer_device_filter`).
This commit is contained in:
2026-10-20 10:02:16 -04:00
parent 569ca25fdb
commit cffbac5771
72 changed files with 7168 additions and 216 deletions
@@ -0,0 +1,22 @@
All green. Report:
**Verified / implemented (final pass of `15_agent_mtls`)**
- Wrote `data/logs/verify-15-tls-e2e.py`: a real `host_agent.py --cert-bundle` + a real dashboard process over a scratch DB (`HOST_TARGETS=loop=https://…`) — **27/27 PASS**, transcript `verify-15-tls-e2e.out`, notes `data/logs/NOTES-tls-rollout.md`.
- Read-only checks against the running dev store: 5 `certs.*` rows byte-identical across a restart; served bundle == the `meta` row (1430 B); 0 events containing `-----BEGIN`; unsigned `/certs`→`303 ?next=`, unsigned `/api/certs`→`401`, signed-in `200`; screenshots `data/logs/15-final-{certs,system}.png`.
- Docs (step 6, full review): README — added *Protecting an agent, host by host* (the rollout task-07 left out), a `tests/test_certs.py`/TLS-matrix/sandbox-installer paragraph, and fixed two stale “the one write endpoint in the app” claims (`NUT_ALLOW_ACTIONS`, Battery-test buttons). AGENTS.md — test count 1424→1427, bundle-fetch command in *Commands*, a Testing-policy bullet on structurally pinning a key-holding module, fleet transport bullet (token mode is now the compatibility path), gap 11 final state.
**Tests / lint (full suite 100% green)**
- `uv run pytest` → **1427 passed, 15 skipped**, exit 0 · `bash .agents/validate.sh` → **exit 0** (coverage **98.94%** vs 94% floor, `certs.py` 100%; ruff “All checks passed”; pyright 0 errors; `node --check` clean; hygiene clean)
- `uv run pytest -m live --live` → **11 passed / 0 failed** · `./scripts/dev-server.sh status` → all 9 sources `ok`
- `uv pip list --outdated` → empty after bumping `fastapi 0.152.0 → 0.153.0` (`uv.lock` only); full suite re-run green.
**Completion criteria**
- Fresh-DB root CA, idempotent: ✅ (tests + live store hashes) · agent TLS matrix incl. wrong-CA/certless 403/`/health` CA-only/revoked 403 + live denylist re-read: ✅
- `https` `host:`/`disks:` `ok` + revoked says “agent certificate revoked” with no dial: ✅ on loopback (real agent + real dashboard) and spy-pinned hermetically; **not** on the fleet (below)
- Signed-in `/certs` + rotate/revoke/root-rotate + byte-identical bundle download: ✅ · installer bundle/unit/`--status` over TLS: ✅ (0640, see deviation)
- `pytest`/`validate.sh`/`--outdated`: ✅ · token-mode byte-identical, engine/UPS untouched, public surface unchanged (route walk): ✅
- **Live fleet TLS (deskwork+gateway flip): NOT done** — no live changes. It needs root on hosts without the installer grant (`deskwork`), has no both-ways-working window (`http://` dashboard vs https-only agent), and would deploy the agent from this not-yet-committed tree. Exact procedure recorded in README + `data/logs/NOTES-tls-rollout.md`.
**Deviations**: bundle mode `0640 root:dashboards-agent` instead of the task file’s `0600` (the agent re-reads its own bundle for the denylist; 0600 breaks the unit — already recorded in AGENTS.md); the loopback dashboard substitutes for the fleet flip. Incidental: a stray `pkill` of mine killed the dev server mid-pass; restarted and verified healthy.
**Next pending phase**: `.agents/phases/todo/16_agent_smart_pass_budget` (then `17_installer_device_filter`).
@@ -0,0 +1,63 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 14%]
........................................................................ [ 19%]
........................................................................ [ 24%]
...........................s............................................ [ 29%]
........................................................................ [ 34%]
........................................................................ [ 39%]
........................................................................ [ 44%]
........................................................................ [ 49%]
........................................................................ [ 54%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................sssssssssss..................... [ 69%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 89%]
........................................................................ [ 94%]
................sss..................................................... [ 99%]
.. [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 436 0 100%
src/dashboards/app.py 185 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 134 0 100%
src/dashboards/sources/host.py 209 0 100%
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3490 37 99%
Required test coverage of 94% reached. Total coverage: 98.94%
1427 passed, 15 skipped, 1 warning in 132.62s (0:02:12)
== 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,20 @@
Everything is green. Final state:
**Implemented**
- `src/dashboards/certs.py` (new, 100% covered): root CA (`CN=dashboards-root`, 10 y) + dashboard client cert (`CN=dashboards`, 1 y) + per-agent server certs (`CN=<host>`, SAN = dialled addresses/IPs, 1 y) — pure lifecycle over the three-method `MetaStore` protocol, five `meta` rows (`certs.root.key/.cert`, `certs.client.key/.cert`, `certs.agents`), `ensure_certs` (stored row always wins; new root takes its client with it), `issue_agent_cert`/`reissue_agent_cert` (`bundle_rev` +1, fresh serial/key, `active`), `revoke` (marks host **and re-seals every bundle** — the `# REVOKED` denylist lives in each), `rotate_root` (new root+client, every agent reissued, denylist reset), `read_agents`/`agent_status`, the install **bundle** (agent cert + agent key + root *cert* + `# BUNDLE-REV`/`# REVOKED`; never the root key), `write_material`/`remove_material` (0700 dir, 0600 files, root key never on disk). No logging, one fixed-text `RuntimeError` naming the `DELETE FROM meta WHERE key LIKE 'certs.%'` reset; no clock except injected `now`.
- `pyproject.toml`/`uv.lock`: `cryptography>=50.0.2` (dashboard-side only; agent stays stdlib `ssl`) + invariant-8 refresh of the four stale transitive pins (certifi/fastapi/starlette/websockets) — suite green after.
- `app.py`/`api.py`: one `build_certs(store)` helper called **before** `build_hub` in both the owned-store and injected-store branches; `AppContext.certs` carries the `ca`/`client_cert`/`client_key` paths for task 03; lifespan removes the private dir on shutdown; corrupt stored PEM = fail-closed startup.
- `tests/test_certs.py` (53 tests): dict `MetaStore`, byte-identity across restarts, chain/EKU/SAN/lifetime shape, rotate/revoke/root-rotate incl. fleet-wide denylist, bundle-never-contains-root-key, 12 corrupt-row fail-closed cases, structural pins (no package import, no logging, only fixed-text raises), real `SampleStore` reopen, a **real `ssl` mTLS handshake** (client cert seen; certless probe still handshakes = task 02's `/health` path), and both app-wiring cases.
**Verification** (all from repo root)
- `uv run pytest` → **1320 passed, 15 skipped**; `uv run pytest tests/test_certs.py` → 53 passed
- `uv run pytest --cov --cov-fail-under=94` → **98.86%** (certs.py 100%, api.py 100%, app.py 100%)
- `uv run ruff check src tests agent` → clean; `uv run pyright` → 0 errors; `bash .agents/validate.sh` → **exit 0**
- `uv pip list --outdated` → empty; `uv run pytest -m live --live` → **11 passed**
- `./scripts/dev-server.sh restart` → all nine sources `ok`; dev DB gained the five `certs.*` rows and they were **byte-identical across two restarts**; the private temp dir belongs to the live process only
**Docs**: README (architecture diagram + `certs.py` bullet + `store.py` meta note), AGENTS.md (invariant 6 extended to private keys, `certs.py` module bullet, `app.py` `build_certs` note, new LOCKED decision for `cryptography`, new gap 11 "CA exists, no exporter uses it yet", test-count/coverage figures), and a one-line correction to `auth.py`'s "no dependency is coming" docstring.
**Decisions/deviations**: first boot also writes `certs.agents` = `{}` (task requires exactly five keys); a newly generated root regenerates the client too (a leaf under a dead root is unusable); every write function returns the new `CertsState` (uniform, store stays the truth); the denylist is fleet-wide by re-sealing all bundles; root rows' chain/key mismatch fails closed rather than self-healing; dependency refresh beyond `cryptography` was needed to satisfy invariant 8.
**Next pending task**: `.agents/phases/todo/15_agent_mtls/02_agent_tls.md`.
@@ -0,0 +1,61 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 5%]
........................................................................ [ 10%]
........................................................................ [ 16%]
........................................................................ [ 21%]
.............................................s.......................... [ 26%]
........................................................................ [ 32%]
........................................................................ [ 37%]
........................................................................ [ 43%]
........................................................................ [ 48%]
........................................................................ [ 53%]
........................................................................ [ 59%]
........................................................................ [ 64%]
.....sssssssssss........................................................ [ 70%]
........................................................................ [ 75%]
........................................................................ [ 80%]
........................................................................ [ 86%]
........................................................................ [ 91%]
.....................................................sss................ [ 97%]
....................................... [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 358 0 100%
src/dashboards/app.py 184 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 276 0 100%
src/dashboards/collectors.py 155 5 97% 141-142, 146-147, 164
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 130 0 100%
src/dashboards/sources/host.py 199 1 99% 147
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3331 38 99%
Required test coverage of 94% reached. Total coverage: 98.86%
1320 passed, 15 skipped, 1 warning in 112.03s (0:01:52)
== 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,15 @@
**Implemented** (`agent/host_agent.py`, stdlib only; `deploy/` untouched)
- `--cert-bundle`/`AGENT_CERT_BUNDLE` + `resolve_bundle(path) -> (context, bundle_rev, revoked, error)`: greps the three PEM blocks out of one installed bundle by text, writes them `0600` into `/run/dashboards-agent` when usable else `mkdtemp`, builds `PROTOCOL_TLS_SERVER` (`TLSv1_2`, `load_cert_chain`, `load_verify_locations(ca)`, `CERT_OPTIONAL`), then deletes the PEMs — the key lives only in the process.
- `TlsBundle` re-reads `# REVOKED` on `(mtime_ns,size)` change (denylist bites without a restart); handler enforces: 403 `client certificate required` on every path but `/health`, 403 `client certificate revoked` on **every** path; serial matched in both hex (`getpeercert`) and decimal (`certs.py`) spellings.
- Token is not read on the TLS path; `/snapshot` gains additive `tls: true` + `bundle_rev` only in TLS mode; `serve()` logs `… with tls (client cert required, rev <n>)`; off-loopback refusal now names the bundle too; unreadable bundle = exit 2; `AGENT_VERSION` 1.1.0 + SCHEMA/docstring lines.
**Tests / lint / coverage**
- `uv run pytest` → **1350 passed, 15 skipped** (baseline 1320+15; +30 new in `tests/test_agent.py`, certs minted by `dashboards.certs` over a dict store, real handshakes: matching cert / CA-only probe / foreign CA / plaintext probe / revoke→403→rewrite→200 / token+bundle / token mode unchanged / `serve()` start line / 4 `resolve_bundle` error paths).
- `uv run pytest --cov --cov-fail-under=94` → **98.86%** (gate met, no drop); `uv run ruff check src tests agent` clean; `uv run pyright` 0 errors; `bash .agents/validate.sh` → **exit 0**; `uv pip list --outdated` empty; `uv run pytest -m live --live` → **11 passed**; `./scripts/dev-server.sh status` → all 9 sources `ok`.
- Hand proof with **curl**: `/health` CA-only 200, `/snapshot` with client pair `tls=true bundle_rev=1`, certless 403, `http://` reset, `# REVOKED` appended while running → 403 revoked on `/snapshot` and `/health`.
**Docs**: README (agent section: `--cert-bundle` usage + TLS-mode paragraph + "token *or* bundle"), AGENTS.md (agent bullet TLS block, gap 11 updated to "agent has TLS mode, nothing dials it yet", test count 1350). Report at `.agents/reports/15_agent_mtls/15_agent_mtls__02_agent_tls.a1.md`.
**Decisions**: (1) a revoked cert is refused on `/health` too — stricter than the task's literal wording, cannot break the installer/systemd probe (it holds the CA, no client cert); (2) denylist matches both serial spellings (a false match can only deny, never allow); (3) unpacked PEMs deleted after load; (4) `main()` tests now `delenv AGENT_*` so a dev env can't turn one into a hang.
**Next**: `.agents/phases/todo/15_agent_mtls/03_collector_tls.md`.
@@ -0,0 +1,61 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 5%]
........................................................................ [ 10%]
........................................................................ [ 15%]
........................................................................ [ 21%]
........................................................................ [ 26%]
...s.................................................................... [ 31%]
........................................................................ [ 36%]
........................................................................ [ 42%]
........................................................................ [ 47%]
........................................................................ [ 52%]
........................................................................ [ 58%]
........................................................................ [ 63%]
...................................sssssssssss.......................... [ 68%]
........................................................................ [ 73%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 89%]
........................................................................ [ 94%]
...........sss....................................................... [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 358 0 100%
src/dashboards/app.py 184 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 276 0 100%
src/dashboards/collectors.py 155 5 97% 141-142, 146-147, 164
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 130 0 100%
src/dashboards/sources/host.py 199 1 99% 147
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3331 38 99%
Required test coverage of 94% reached. Total coverage: 98.86%
1350 passed, 15 skipped, 1 warning in 115.46s (0:01:55)
== 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,15 @@
**Implemented (task 03 — collector TLS)**
- `collectors.AgentTls` (`verify` CA path, `cert` pair, live `status` callable, test-only `transport`) + `agent_tls_map(material, store, targets)`; `fetch_snapshot(..., tls=...)` now dials an `https://` `HOST_TARGETS` entry with this app's client certificate, verified against this app's own root CA.
- Revocation happens **before any socket**: a host whose `certs.agents` status is `revoked` raises `UpstreamError("<host>: agent certificate revoked")`, so both `host:<name>` and `disks:<name>` go red on the next poll with no restart and no dial; `http://` token hosts still use the shared client byte-for-byte.
- 403 from a protected agent reports `agent refused the client certificate (<agent's own detail>) at <redacted url>`; token-mode refusals keep the 401/HTTP wording.
- Plumbing: `build_certs` → `agent_tls_map` → `build_hub(..., tls=...)` in both assembly branches; `AppContext.certs`; `agent_tls_fields()` carries `tls`/`bundle_rev` into the `system` and `disks` payloads (additive, no `severity`).
- Defect found and fixed in task 01's `certs.py`: no SKI/AuthorityKeyIdentifier, so a **strict** verifier — `ssl.create_default_context()`, which this dial builds — refused the chain with `CERTIFICATE_VERIFY_FAILED: Missing Authority Key Identifier`; the agent's non-strict server check hid it. Pinned in `tests/test_certs.py`.
**Checks:** `uv run pytest` 1371 passed / 15 skipped (21 new: `test_collectors.py` 31→56 incl. a real `host_agent.py` served over TLS, `test_api.py` revoked-through-real-`build_hub`, `test_certs.py` identifiers) · coverage 98.91 % (floor 94; new code 100 %, collectors' 5 misses are pre-existing `/slots`+`NutState` lines) · ruff + pyright clean · `bash .agents/validate.sh` exit 0 · live suite with `.env.dev` 11 passed · dev server restarted, all 9 sources ok.
**Docs:** README (agent TLS section + collector's half, `HOST_TARGETS` row, `certs.py` bullet), AGENTS.md (agent/collectors/certs/app/host bullets, gap 11, tallies), `.env.example` + `.env.dev.example`; evidence in `data/logs/NOTES-collector-tls.md`.
**Deviation:** httpx 0.28 has no per-request `verify`/`cert` (both deprecated), so the material rides on a **per-dial client built from an `ssl.SSLContext`** — same isolation the task wanted (shared engine/plain client untouched), stricter verification. No live infrastructure changed: no agent runs a bundle yet.
**Next:** `.agents/phases/todo/15_agent_mtls/04_cert_api.md`.
@@ -0,0 +1,64 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 5%]
........................................................................ [ 10%]
........................................................................ [ 15%]
........................................................................ [ 20%]
........................................................................ [ 25%]
....s................................................................... [ 31%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 51%]
........................................................................ [ 57%]
........................................................................ [ 62%]
........................................................sssssssssss..... [ 67%]
........................................................................ [ 72%]
........................................................................ [ 77%]
........................................................................ [ 83%]
........................................................................ [ 88%]
........................................................................ [ 93%]
................................sss..................................... [ 98%]
.................. [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 358 0 100%
src/dashboards/app.py 184 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 276 0 100%
src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 134 0 100%
src/dashboards/sources/host.py 209 0 100%
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3395 37 99%
Required test coverage of 94% reached. Total coverage: 98.91%
1371 passed, 15 skipped, 1 warning in 122.72s (0:02:02)
== 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 ==
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
fastapi 0.150.0 0.151.0 wheel
@@ -0,0 +1,14 @@
All green. Final state:
**New / changed**
- `src/dashboards/certs.py` — `read_state()`: the request-time read (validates, never mints/writes); `ensure_certs()` stays the startup-only bootstrap.
- `src/dashboards/api.py` — `certs_router()`: `GET /api/certs` (root/client + one row per `HOST_TARGETS` ∪ `certs.agents` name, `status:"unissued"` for configured-never-issued, `deployed_bundle_rev` from the `system` payload's `agent` block, serials as decimal strings, no severity); `GET /api/certs/<name>/bundle` (stored bytes verbatim, `application/x-pem-file`, `attachment; filename="agent-<name>.bundle"`, `no-store`); `POST …/rotate` (SAN from live config, first-issue = rev 1), `POST …/revoke`, `POST /api/certs/root/rotate` (`restart_required:true`). Unknown name → `404 {"configured": false}` before any signing/dial/store write; writes log one fixed-text `certs`-source event. `app.py` includes the router.
- No new dial: the only upstream write stays `POST /api/ups/command`; `/api/certs/*` writes are a third, `meta`-only class in the invariant-1 pin.
**Tests** — `tests/test_api.py`: 22 cert tests (list shapes 0/1/3 hosts, ghost entry read-only, no-dial/no-write spies, rev+1 + old serial absent from the new denylist, revoke → next `fetch_snapshot` refuses without a socket, root rotate re-issues all + `restart_required`, bundle byte-identity + reissue changes bytes, no PEM on `/api/certs`/`/api/health`/`/api/events`/`/api/summary`/`/api/config`, GET cannot issue). `tests/test_auth_api.py`: unsigned JSON `401` on list/bundle/writes, missing-CSRF and cross-origin refusals with the table unmoved. `tests/test_certs.py`: `read_state` reports the stored authority after a rotate and refuses corrupt/mismatched rows with the fixed text, never writing.
**Verification** — `bash .agents/validate.sh` exit 0: 1400 passed + 15 skipped, coverage 98.94% (`api.py` 100%, `certs.py` 100%), ruff clean, pyright 0 errors. Dev server restarted: `/api/health` all nine sources ok, `GET /api/certs` unsigned → `401`, signed-in reads the real CA (root/client serials, three hosts `unissued`). No fleet or agent config touched.
**Docs** — README "Managing the certificates" section + `api.py` bullet + scripting note; AGENTS.md `api.py`/`certs.py` bullets, invariant-1 write lists, gap 11, test/coverage counts.
**Needs attention** — pre-existing advisory only: `fastapi 0.150.0 → 0.151.0` (no dependency moved in this task; left for the phase's dependency pass).
@@ -0,0 +1,64 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 5%]
........................................................................ [ 10%]
........................................................................ [ 15%]
........................................................................ [ 20%]
........................................................................ [ 25%]
.................s...................................................... [ 30%]
........................................................................ [ 35%]
........................................................................ [ 40%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 61%]
........................................................................ [ 66%]
.............sssssssssss................................................ [ 71%]
........................................................................ [ 76%]
........................................................................ [ 81%]
........................................................................ [ 86%]
........................................................................ [ 91%]
.............................................................sss........ [ 96%]
............................................... [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 436 0 100%
src/dashboards/app.py 185 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 134 0 100%
src/dashboards/sources/host.py 209 0 100%
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3490 37 99%
Required test coverage of 94% reached. Total coverage: 98.94%
1400 passed, 15 skipped, 1 warning in 124.11s (0:02:04)
== 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 ==
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
fastapi 0.150.0 0.151.0 wheel
@@ -0,0 +1,10 @@
**Task 15.05 complete — gate green.**
- **New:** `web/pages/certs.html` (Root CA block: CN/serial/expiry + `in 10.0y` + `Rotate root CA`; display-only Dashboard client line; `#agents-table`; empty state; two `role="status"` outcome lines; 5 tabs, no `aria-current`, no `#nav-status`) and `web/static/page-certs.js` (reads `GET /api/certs`; a `COLUMNS` table names every key it indexes; `status` word → chip class with `unissued` mapping to *no* class; `n/a` for a null `deployed_bundle_rev`; neutral `stale` chip when issued > deployed; `confirm()`ed writes printing the route's own `hint`).
- **`app.py`** — `/certs` in `PAGES`, so the gate guards it with no new code. **`dash.css`** — one rule (`table.data td.san`, the only cell that must wrap); the account outcome line is documented as shared, no new tokens.
- **Masthead** — `⚷ Certs` added to the five dashboards *and* `/settings`; `/certs` carries `⚙ Account`; neither admin page self-links (the `/settings` precedent).
- **Tests** — `/certs` joins `MARKUP_PAGES`/`PAGE_SCRIPTS`/`ASSETS` (CDN, `.sev-`, flavour-text, orphan-`id` audits); `test_every_dashboard_page_links_to_settings` extended into `test_every_page_links_to_both_admin_pages`; 6 new pins, incl. `COLUMNS`↔`CERT_AGENT_KEYS` resolved against a live `/api/certs` row in both directions, the CNs pinned to `certs.ROOT_CN`/`CLIENT_CN`, no `severity` anywhere on the page, no control the API would refuse, and the restart sentence being the server's `hint`; `test_auth_api` gained `/certs` in `PROTECTED_PAGES` and both served-route tables.
- **Fixed in passing** — one `api.py` comment claimed task 05's page reads `source: "certs"` events; it does not, so the comment now names `/api/events?source=certs` as the audit trail.
- **Gate** — `bash .agents/validate.sh` exit 0: **1408 passed, 15 skipped**, coverage **98.94 %**, ruff + pyright clean, `node --check` clean; dev server restarted (all nine sources `ok`), unsigned `GET /certs` → `303 /login?next=/certs`.
- **Browser** (`data/logs/`) — `15-certs-page.png` (live: three `unissued` rows), `-masthead.png`, `-masthead-settings.png`, `15-certs-rotated.png` (real Rotate → `active`, serial, `#1 → n/a`, `install the new bundle on deskwork — /api/certs/deskwork/bundle`), `15-certs-shapes.png` + `-shapes-dark.png` (stale chip, revoked, `expired`, unissued, `not in HOST_TARGETS`) — the last two used an intercepted `page.route` payload, since the live fleet has no bundles until task 07. That pass issued one real certificate for `deskwork` (`bundle_rev 1`) in the dev store.
- **Docs** — README page list, a `/certs` block in *Managing the certificates*, the signing-in scope line; AGENTS intro, the `web/` bullet (both rules the page keeps), and gap 11 now tasks 01–05 landed with 06/07 left.
@@ -0,0 +1,64 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 5%]
........................................................................ [ 10%]
........................................................................ [ 15%]
........................................................................ [ 20%]
........................................................................ [ 25%]
........................s............................................... [ 30%]
........................................................................ [ 35%]
........................................................................ [ 40%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 60%]
........................................................................ [ 65%]
.....................sssssssssss........................................ [ 70%]
........................................................................ [ 75%]
........................................................................ [ 80%]
........................................................................ [ 86%]
........................................................................ [ 91%]
.....................................................................sss [ 96%]
....................................................... [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 436 0 100%
src/dashboards/app.py 185 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 134 0 100%
src/dashboards/sources/host.py 209 0 100%
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3490 37 99%
Required test coverage of 94% reached. Total coverage: 98.94%
1408 passed, 15 skipped, 1 warning in 125.83s (0:02:05)
== 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 ==
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
fastapi 0.150.0 0.151.0 wheel
@@ -0,0 +1,8 @@
**Report — task `15_agent_mtls/06_deploy_cert_bundle`**
- `deploy/install-agent.sh`: new `--cert-bundle <file>` flag; `validate_bundle()` (the `resolve_bundle` text-shape idiom: 2 certs + exactly 1 key + `# BUNDLE-REV`) runs **before `need_root`**, exit 2 with the reason, touches nothing, never dials; bundle installed to `/etc/dashboards-agent.certs` byte-identically, re-runs without the flag keep it (token-reuse idiom); the unit's TLS branch (one `if [ -f "$CERT_FILE" ]` block) adds `--cert-bundle` + `RuntimeDirectory=dashboards-agent` (`0700`) while keeping `--token-file`; `--status` probes `https` with the bundle's CA, `CERT_REQUIRED` + no client cert (`python3` `ssl` one-liner, no openssl), unreadable bundle → "run --status as root"; token mode keeps the plain curl probe; `--uninstall` sweeps the bundle; TLS install prints the `https://` `HOST_TARGETS` hint. `deploy/agent-sudoers` unchanged.
- **Deviation (deliberate):** the task's literal `0600` bundle mode cannot work — the agent opens the bundle as its own user and re-reads the denylist live (the exact incident the token file's comment records); installed at `0640 root:dashboards-agent`, which is the task's stated intent ("only root and the agent's own service user may read it") and the token-file idiom. Pinned in the script comment, the tests, and AGENTS.md gap 11.
- Tests: 16 new pins in `tests/test_deploy_scripts.py` (validation refusal matrix incl. `/nonexistent` before the sudoers hint; well-formed bundle reaches the root check; help/path/mode/chown pins; one-branch TLS unit lines; probe text pins incl. "no `load_cert_chain`, never openssl"; sandbox install → file 0640 + byte-identical + unit flags + `RuntimeDirectory`; bundle reuse; uninstall; plus end-to-end `--status` against a live in-process TLS agent — CA-only `/health` pass, foreign-root refusal, unreadable-bundle sentence, token-mode plain probe).
- `bash .agents/validate.sh` → exit 0: **1424 passed, 15 skipped**, coverage 98.94% (floor 94), ruff clean, pyright 0 errors, `node --check` clean, `uv pip list --outdated` empty (bumped fastapi 0.150→0.152 per invariant 8; suite re-run green).
- Docs: README (installer TLS install + `--status` note + installer paragraph in the TLS-mode section); AGENTS.md (deployment paragraph flags/bundle/RuntimeDirectory/probe, gap 11 rewritten to "what is left is task 07", test count 1424).
- Next pending task: `15_agent_mtls/07_live_rollout` (live infra: flips deskwork+gateway to TLS — user/operator gated, untouched here).
@@ -0,0 +1,62 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 5%]
........................................................................ [ 10%]
........................................................................ [ 15%]
........................................................................ [ 20%]
........................................................................ [ 25%]
........................s............................................... [ 30%]
........................................................................ [ 35%]
........................................................................ [ 40%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 60%]
........................................................................ [ 65%]
.....................................sssssssssss........................ [ 70%]
........................................................................ [ 75%]
........................................................................ [ 80%]
........................................................................ [ 85%]
........................................................................ [ 90%]
........................................................................ [ 95%]
.............sss....................................................... [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 436 0 100%
src/dashboards/app.py 185 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 134 0 100%
src/dashboards/sources/host.py 209 0 100%
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3490 37 99%
Required test coverage of 94% reached. Total coverage: 98.94%
1424 passed, 15 skipped, 1 warning in 125.49s (0:02:05)
== 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,16 @@
**Target task** (`.agents/phases/todo/16_agent_smart_pass_budget/01_pin_the_scaled_budget.md` — the harness's stated name `01_live_remeasure_and_pin.md` does not exist; the phase dir has exactly one task 01):
- `tests/test_agent.py`: added `FakeSmartPassClock` + `_TimeProxy` (drives the agent's `time.monotonic` and `subprocess.run` through proxies on `host_agent`'s own globals, recording the `min(deadline-now, 5 s)` each device was given — a deadline is invisible to a fake binary), `SHELF_DISKS`/`write_disk_shelf` (driveripper's 8 NVMe + 8 SATA shape), and 3 tests: `test_the_pass_budget_scales_with_the_disk_count` (16 disks, 24 s pass → all 16 asked, no budget line), `test_the_pass_budget_floor_is_twenty_seconds` (2nd device's timeout = 2 s → pins the 20 s floor), `test_the_pass_budget_error_line_names_the_scaled_figure` (6 s/device → 14 runs, 2 skipped, line is exactly `smart: pass budget of 80s exhausted, 2 disk(s) skipped`).
- Non-tautology proven by mutation: reverting `_smart_pass` to the flat `budget = self.smart_budget_seconds` makes the two scaling tests fail (`4 != 14` calls); `agent/host_agent.py` restored, md5 verified `4061ad59…` — **no code change**.
- Override test `test_the_pass_budget_skips_the_rest_with_one_error` untouched and green.
**Tests / lint (exact, all from repo root):**
`uv run pytest` → **1427 passed, 15 skipped** (baseline 1424+15); `uv run pytest --cov --cov-fail-under=94` → **98.94%**; `uv run ruff check src tests agent` → All checks passed; `uv run pyright` → 0 errors; `bash .agents/validate.sh` → **exit 0**; `uv run pytest tests/test_agent.py -q --cov=agent` → `agent/host_agent.py` 94 %, budget lines 584–602 fully covered; `uv run pytest -m live --live` → **11 passed**; `./scripts/dev-server.sh status` → all 9 sources ok.
**Docs:** no doc impact — tests-only task. Deliberately left for task `02_docs_and_gate.md`: `README.md:94` still reads "20 s per pass" (`grep -rn "20 s per pass" AGENTS.md README.md agent/ deploy/` → currently only that one hit).
**Decisions / findings:**
- No live/infra changes made for this task. Read-only md5 probe: repo agent ≠ deployed agent on all three hosts (working tree carries phase 15's un-deployed TLS mode), so the phase's md5-identity criterion can't be met yet; the meaningful check passes — driveripper's deployed agent **does** carry the scaled-budget line (deskwork/gateway don't, and can't be affected: 1 disk / no SMART, floor == scaled there).
- **Interrupted work (harness redirect):** phase 15 task `07_live_rollout` was mid-flight when the target changed. I made **no fleet mutations** and **no tracked-file changes** for it, and cleaned up all scratch processes (`:8091` scratch dashboard, `:9129` TLS agent removed; dev server untouched and healthy). Evidence captured in `data/logs/`: `probe-tls-matrix.py` (6/6 PASS: CA-only `/health` 200, certless `/snapshot` 403, dashboard client 200 with `tls/bundle_rev`, foreign-root client `TLSV1_ALERT_UNKNOWN_CA`, agent's own server cert refused as a client cert, untrusted-root `CERTIFICATE_VERIFY_FAILED`), byte-identical bundle download, revoke → `agent certificate revoked` with zero new agent-log lines, rotate → `#2 → #2`, live `# REVOKED` denylist re-read without restart, screenshots `15-tls-local-{certs,system}.png`, `15-tls-fleet-certs.png`. Still outstanding there: the README "Protecting the agent" how-to, the AGENTS.md transport/commands/gap-11 update, and the `NOTES-agent-install.md` operator append.
**Next pending task:** `.agents/phases/todo/16_agent_smart_pass_budget/02_docs_and_gate.md`.
@@ -0,0 +1,63 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 14%]
........................................................................ [ 19%]
........................................................................ [ 24%]
...........................s............................................ [ 29%]
........................................................................ [ 34%]
........................................................................ [ 39%]
........................................................................ [ 44%]
........................................................................ [ 49%]
........................................................................ [ 54%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................sssssssssss..................... [ 69%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 89%]
........................................................................ [ 94%]
................sss..................................................... [ 99%]
.. [100%]
=============================== 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
-- 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% 32
src/dashboards/alerts.py 176 0 100%
src/dashboards/api.py 436 0 100%
src/dashboards/app.py 185 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307
src/dashboards/config.py 120 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183
src/dashboards/rates.py 73 0 100%
src/dashboards/severity.py 49 0 100%
src/dashboards/sources/__init__.py 0 0 100%
src/dashboards/sources/disks.py 134 0 100%
src/dashboards/sources/host.py 209 0 100%
src/dashboards/sources/llm.py 178 1 99% 285
src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736
src/dashboards/sources/nut_sim.py 134 0 100%
src/dashboards/store.py 313 2 99% 553-554
------------------------------------------------------------------
TOTAL 3490 37 99%
Required test coverage of 94% reached. Total coverage: 98.94%
1427 passed, 15 skipped, 1 warning in 133.41s (0:02:13)
== 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 ==