phase: 33_gateway_route_switching

Phase 33 completion pass is done. Final report:

## Phase 33 — completion report

**All 8 task files complete** (`.agents/phases/complete/33_gateway_route_switching/`), full gate green, docs verified.

### Work in this pass

1. **`tests/test_routectl.py` added** (17 tests, all green) — exercises the *real* helper binary unprivileged against a fake tree (`--root`), the real agent parser, the captured gateway fixtures, and stubbed `nginx`/`haproxy`/`systemctl` on `PATH`. Pins the full protocol (locate → state → dry-run → temp-file/fsync/`os.replace` write → checker → restore on checker failure → reload-never-restart → re-read proof → sidecar record), all three route shapes (haproxy `server`, nginx `proxy_pass`, nginx stream `map` via its sink-value id), `drift` / unknown-id / `map-default` refusals, `list`/`verify`, and the `--serve` peer gate (`SO_PEERCRED` uid check, one JSON line per request, exact body shape).

2. **Real helper defect fixed** — `deploy/dashboards-routectl.py`: `main()`'s early-return paths returned `({"ok": False, …}, EXIT_*)` tuples, so `sys.exit()` printed a Python repr instead of a JSON line (usage errors, agent-import refusal). Now prints `json.dumps(…)` and returns only the code, keeping the one-JSON-line contract.

3. **Documentation consistency fixes** (task 08 criterion 6):
   - `README.md`: garbled verdict-colour sentence on the switch ("green and to the position") → "in the **on** position and tinted `--gate-on`" (the existence-pair contract).
   - `README.md`: audit sentence now names all three event kinds — `route-disabled` / `route-enabled` / **`route-denied`** (the code stores one per refused call; dry runs store none).
   - `page-gateway.js`: replaced the stale pre-33 module docstring ("the switch is not wired") with the two-arm `GATEWAY_ALLOW_WRITES` contract.
   - `AGENTS.md`: every phase-33 claim re-verified against code (the ten gates, the 35 s/30 s budget pair, `gateway.writes_enabled`, `/api/summary`'s `disabled`, the 1.4.0 agent bullet, the gap list) — no changes needed.

### Verification
- `uv run pytest --cov`: **1970 passed**, 18 skipped — coverage **98.74%** (floor 94%)
- `ruff` clean · `pyright` 0 errors · `bash .agents/validate.sh` **exit 0** (incl. `node --check` on every page script)
- Dev server running; all 10 sources `ok`; bundles exported

### Live infrastructure
**No live changes made.** The live drill remains unexecuted per `data/logs/NOTES-33-switching.md` (no operator approval; checker binaries unavailable in the podman proxy units). All routes stay enabled, config hashes unchanged, and the sandbox rehearsal + backup tree are in place for the operator's own `DANGER` call. All changes are in the working tree only.
This commit is contained in:
2026-11-01 11:50:51 -05:00
parent 0271387053
commit 86c8bf4992
91 changed files with 7568 additions and 519 deletions
@@ -0,0 +1,25 @@
Phase 33 completion pass is done. Final report:
## Phase 33 — completion report
**All 8 task files complete** (`.agents/phases/complete/33_gateway_route_switching/`), full gate green, docs verified.
### Work in this pass
1. **`tests/test_routectl.py` added** (17 tests, all green) — exercises the *real* helper binary unprivileged against a fake tree (`--root`), the real agent parser, the captured gateway fixtures, and stubbed `nginx`/`haproxy`/`systemctl` on `PATH`. Pins the full protocol (locate → state → dry-run → temp-file/fsync/`os.replace` write → checker → restore on checker failure → reload-never-restart → re-read proof → sidecar record), all three route shapes (haproxy `server`, nginx `proxy_pass`, nginx stream `map` via its sink-value id), `drift` / unknown-id / `map-default` refusals, `list`/`verify`, and the `--serve` peer gate (`SO_PEERCRED` uid check, one JSON line per request, exact body shape).
2. **Real helper defect fixed** — `deploy/dashboards-routectl.py`: `main()`'s early-return paths returned `({"ok": False, …}, EXIT_*)` tuples, so `sys.exit()` printed a Python repr instead of a JSON line (usage errors, agent-import refusal). Now prints `json.dumps(…)` and returns only the code, keeping the one-JSON-line contract.
3. **Documentation consistency fixes** (task 08 criterion 6):
- `README.md`: garbled verdict-colour sentence on the switch ("green and to the position") → "in the **on** position and tinted `--gate-on`" (the existence-pair contract).
- `README.md`: audit sentence now names all three event kinds — `route-disabled` / `route-enabled` / **`route-denied`** (the code stores one per refused call; dry runs store none).
- `page-gateway.js`: replaced the stale pre-33 module docstring ("the switch is not wired") with the two-arm `GATEWAY_ALLOW_WRITES` contract.
- `AGENTS.md`: every phase-33 claim re-verified against code (the ten gates, the 35 s/30 s budget pair, `gateway.writes_enabled`, `/api/summary`'s `disabled`, the 1.4.0 agent bullet, the gap list) — no changes needed.
### Verification
- `uv run pytest --cov`: **1970 passed**, 18 skipped — coverage **98.74%** (floor 94%)
- `ruff` clean · `pyright` 0 errors · `bash .agents/validate.sh` **exit 0** (incl. `node --check` on every page script)
- Dev server running; all 10 sources `ok`; bundles exported
### Live infrastructure
**No live changes made.** The live drill remains unexecuted per `data/logs/NOTES-33-switching.md` (no operator approval; checker binaries unavailable in the podman proxy units). All routes stay enabled, config hashes unchanged, and the sandbox rehearsal + backup tree are in place for the operator's own `DANGER` call. All changes are in the working tree only.
@@ -0,0 +1,71 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 10%]
........................................................................ [ 14%]
........................................................................ [ 18%]
........................................................................ [ 21%]
........................................................................ [ 25%]
........................................................................ [ 28%]
........................................................................ [ 32%]
.........................s.............................................. [ 36%]
........................................................................ [ 39%]
........................................................................ [ 43%]
........................................................................ [ 47%]
........................................................................ [ 50%]
........................................................................ [ 54%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 68%]
........................................................................ [ 72%]
...................................................ssssssssssssss....... [ 76%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 86%]
........................................................................ [ 90%]
........................................................................ [ 94%]
..........................................................sss........... [ 97%]
............................................ [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/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.8-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 201 0 100%
src/dashboards/api.py 625 9 99% 229, 1273-1275, 1310-1313, 1382, 1390, 1446, 1449
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 244 5 98% 386-387, 391-392, 409
src/dashboards/config.py 128 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/gateway.py 613 10 98% 339, 588, 601-602, 652, 785, 1089, 1140, 1207, 1569
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 4369 56 99%
Required test coverage of 94% reached. Total coverage: 98.72%
1970 passed, 18 skipped, 1 warning in 192.42s (0:03: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 @@
Let me look at the agent's file-reading helpers and the `parse_nginx_config`/`nginx_include_paths` functions.
@@ -0,0 +1,69 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 11%]
........................................................................ [ 15%]
........................................................................ [ 19%]
........................................................................ [ 23%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................s............................... [ 34%]
........................................................................ [ 38%]
........................................................................ [ 42%]
........................................................................ [ 46%]
........................................................................ [ 50%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 73%]
.........................sssssssssssss.................................. [ 76%]
........................................................................ [ 80%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..............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.8-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 201 0 100%
src/dashboards/api.py 491 1 99% 220
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 216 5 98% 328-329, 333-334, 351
src/dashboards/config.py 126 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/gateway.py 584 8 99% 315, 558, 569, 678, 971, 1022, 1089, 1451
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 4176 46 99%
Required test coverage of 94% reached. Total coverage: 98.90%
1855 passed, 17 skipped, 1 warning in 201.21s (0:03:21)
== 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 @@
Now let me look at the existing deploy-script test harness and the agent's unit template in `install-agent.sh`, plus the agent's main guard.
@@ -0,0 +1,69 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 11%]
........................................................................ [ 15%]
........................................................................ [ 19%]
........................................................................ [ 23%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................s............................... [ 34%]
........................................................................ [ 38%]
........................................................................ [ 42%]
........................................................................ [ 46%]
........................................................................ [ 50%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 73%]
.........................sssssssssssss.................................. [ 76%]
........................................................................ [ 80%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..............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.8-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 201 0 100%
src/dashboards/api.py 491 1 99% 220
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 216 4 98% 329, 333-334, 351
src/dashboards/config.py 126 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 4 96% 126, 163, 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/gateway.py 584 8 99% 315, 558, 569, 678, 971, 1022, 1089, 1451
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 4176 44 99%
Required test coverage of 94% reached. Total coverage: 98.95%
1855 passed, 17 skipped, 1 warning in 175.99s (0:02: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 @@
Now let me look at the dashboard side — the `connections[].id` scheme in `sources/gateway.py`, the Handler and `serve`/`main` in the agent, and the phase-32 additivity control script.
@@ -0,0 +1,71 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 11%]
........................................................................ [ 15%]
........................................................................ [ 19%]
........................................................................ [ 23%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................s............................... [ 34%]
........................................................................ [ 38%]
........................................................................ [ 42%]
........................................................................ [ 46%]
........................................................................ [ 50%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 73%]
.........................sssssssssssss.................................. [ 76%]
........................................................................ [ 80%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..............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.8-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 201 0 100%
src/dashboards/api.py 491 1 99% 220
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 216 5 98% 328-329, 333-334, 351
src/dashboards/config.py 126 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/gateway.py 584 8 99% 315, 558, 569, 678, 971, 1022, 1089, 1451
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 4176 46 99%
Required test coverage of 94% reached. Total coverage: 98.90%
1855 passed, 17 skipped, 1 warning in 174.63s (0:02:54)
== 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):
ruff 0.17.2 0.17.3 wheel
@@ -0,0 +1,28 @@
Task 04 is complete. Short report:
## Task 04 — `enabled` joins the payload, `off` becomes reachable, warnings learn the marker
**Verdict: done.** All completion criteria met; `bash .agents/validate.sh` **exit 0**.
### What this run added (the tree carried the source work from the earlier session; I verified every behaviour and added the missing mandated surface)
- **`tests/conftest.py`** — two new fixture modes, both the *captured* fleet tree plus an edit, run through the real agent `Collector` on a fake `--root`: `marked` (the helper's three edits: `gitea` `server` line, nextcloud vhost `proxy_pass`, `brain` map entry → sink, each with the exact `#off <name>` trailing comment, plus the sink block in `stream.d/00-dashboards-sink.conf`) and `hand_edited` (same three shapes, markers removed).
- **`tests/test_gateway_source.py`** — the six mandated tests: three-marker fixture → three `enabled: false` rows with selectors/endpoints intact, `warnings == []` with the shapes proven present (suppression, not vacuous); the marker-vs-hand-deleted **pair** (silence ↔ `haproxy_backend_empty` + `nginx_map_route_missing`); sink recognised by block, not a hard-coded port; `0` only when the table answered, `null` + no samples when it didn't; stub/answer key-set parity (stub's `None` = absence of the claim).
- **`tests/test_api.py`** — `disabled` restated in the pinned 13-key set with its reason; summary test gains `marked` (=3) and `hand_edited` (=0) beside `full` (=0); the overview-line test learns `GATEWAY_DISABLED_FIELD` and pins the `> 0` / `Number.isInteger` guards (`0` prints nothing, `null` prints nothing).
- **`page-index.js`** — the `switched off` tally clause beside the `warnings` one, same guard.
- **Docs** — README + AGENTS.md name `enabled`/`off`, the `#off` id suffix, and the marker-aware warnings rule.
- **Rule 8** — `uv pip list --outdated` was non-empty before the gate; updated on purpose (pytest 9.2.0 → needed `asyncio_default_fixture_loop_scope = "function"` in `pyproject.toml`, pydantic 2.14.2, ruff 0.17.3, websockets 17.4). Now empty.
### Evidence
- **Gate**: 1877 passed / 17 skipped · coverage 98.86% (floor 94%; `gateway.py` 98%, `api.py` 99% — both >90%) · ruff clean · pyright 0 · `node --check` clean.
- **Live (rule 10)**: dev server restarted; all sources ok; live `/api/summary` serves `gateway: {… 'disabled': 0, 'connections': 33, 'warnings': 0}` (the real fleet has no markers — honest zero, live config untouched); browser pass on the overview page: `gateway 10 listeners · 33 connections · 15 live in · 15 live out` — four figures, **no `· 0 switched off` clause**, exactly the empty-state rule restated for the switch. Screenshot: `data/logs/gateway-switch-04-overview-line.png`.
- Report written to `.agents/reports/33_gateway_route_switching/…04….a2.md`; ntfy sent.
### Flags (left to the driver/operator)
1. **Task 05's `complete/` copy is from an aborted run** — its code is **not** in the tree (no `POST /api/gateway/route`, no `GATEWAY_ALLOW_WRITES`, no `collectors.post_route`), and its transcript ends mid-work. 05 needs a clean re-run; its `todo/` copy is still there, which is what the driver looks for.
2. **04's `todo/` copy is orphaned** (identical file in `complete/`; 01–03 follow the move convention). Per the working protocol I didn't touch `todo/` — the driver's move step should remove it.
3. None of the phase-33 work is committed yet.
Ready for task 05 on your go — or let the pipeline re-run it.
@@ -0,0 +1,70 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 11%]
........................................................................ [ 15%]
........................................................................ [ 19%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 30%]
...............................................s........................ [ 34%]
........................................................................ [ 38%]
........................................................................ [ 41%]
........................................................................ [ 45%]
........................................................................ [ 49%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 60%]
........................................................................ [ 64%]
........................................................................ [ 68%]
........................................................................ [ 72%]
...............................................sssssssssssss............ [ 76%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 91%]
........................................................................ [ 95%]
....................................sss................................. [ 98%]
...................... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/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.8-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 201 0 100%
src/dashboards/api.py 492 1 99% 220
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 216 5 98% 328-329, 333-334, 351
src/dashboards/config.py 126 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/gateway.py 613 10 98% 339, 588, 601-602, 652, 785, 1089, 1140, 1207, 1569
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 4206 48 99%
Required test coverage of 94% reached. Total coverage: 98.86%
1877 passed, 17 skipped, 1 warning in 171.02s (0:02:51)
== 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,110 @@
# Task 04 completion report (attempt 2, manual run)
**Status:** complete — all completion criteria met, `bash .agents/validate.sh` green (exit 0).
Note: attempt `a1`'s transcript died mid-work (it ends at "Now let me read the current
`warnings()` and `config_connections()` in the source:") yet its `.status` read `ok`; the
`a1` report for task 05 likewise claims tasks 01–04 were unimplemented and was itself
aborted. The tree carried the task-04 source work from the earlier session; this run
verified every mandated behaviour end-to-end and added the missing mandated tests, the
two fixture modes, the overview clause and its pins, and the doc sentences.
## What this run added (on top of the pre-existing task-04 source work)
- `tests/conftest.py` — two new `GATEWAY_MODES`, both the **captured** tree plus an edit,
run through the real agent `Collector` with a fake `--root` tree (wire-shape renames
fail tests, never draw empty pipes):
- `marked` — the helper's three edits: the `gitea` `server` line, the nextcloud vhost
`proxy_pass` line and the `brain.reeseapps.com` stream `map` entry (value rewritten
to the sink `127.0.0.1:44450`) each carrying the agent's `#off <name>` trailing
comment (the whole comment, one name — `split_marker`'s grammar), plus the helper's
sink `server` block in `stream.d/00-dashboards-sink.conf`.
- `hand_edited` — the same three shapes without the markers: the `server` line deleted,
the `proxy_pass` line plain, the map entry's value deleted, no sink file.
- `tests/test_gateway_source.py` — the six mandated tests, all passing:
- `test_a_marker_off_route_is_declared_and_disabled` — the `marked` fixture yields
exactly three `enabled: false` rows, selectors and endpoints intact (`gitea-2222` →
`reeseapps-gitea.reeselink.com:2222`, `nextcloud.reeseapps.com:11000`,
`127.0.0.1:44450`), every other row `true`, `#off` id suffix on every off row,
`warnings == []` with the marker-caused shapes proven present in the raw sections.
- `test_the_source_never_calls_a_route_broken_because_the_operator_disabled_it` /
`test_the_same_shape_without_a_marker_is_still_a_warning` — the pair: marker-caused
shapes warn nothing; the same shapes hand-deleted bring back
`haproxy_backend_empty` (subject `gitea-2222`) and `nginx_map_route_missing`
(subject `targetBackend:brain.reeseapps.com`), with no `#off` row in that arm.
- `test_the_sink_address_is_recognised_not_hard_coded` — a sink block on a different
port (`45999`) still reads as off from the payload's own text; a loopback target
that is *not* the block's port is on.
- `test_a_disabled_line_still_publishes_only_a_measured_count` — table answered and
empty: off rows `0` with a `0.0` sample (measured, never stamped); table absent
(`available: null`): every row `null`, no samples.
- `test_the_stub_and_the_answer_share_one_key_set` — answered rows carry `true`/`false`
(never null, never absent); the stub carries no `enabled`/`disabled` key anywhere
(nobody read the config, so its `None` is the absence of the claim); top-level
parity modulo `unreachable` / `agent_version`.
- plus the extended no-verdict walk (`marked`, `hand_edited` added to the modes).
- `tests/test_api.py` — `GATEWAY_BLOCK_FIELDS` comment restates the thirteenth key with
its reason; `test_the_summary_gateway_block_is_the_payloads_sums...` gains `marked`
(`disabled == 3`) and `hand_edited` (`disabled == 0`, the markers gone → the off state
gone) beside `full`'s `0`; the stub test's `disabled` pin was already in the tree;
`test_the_overview_line_reads_the_gateway_block_it_claims` learns
`GATEWAY_DISABLED_FIELD` (table resolved against the live block) and pins the guard:
printed only `> 0` and `Number.isInteger` — `0` prints nothing, `null` prints nothing.
- `src/dashboards/web/static/page-index.js` — `GATEWAY_DISABLED_FIELD`
(`disabled` / `switched off`) and the second non-zero-guarded clause beside the
`warnings` one.
- `pyproject.toml` — `asyncio_default_fixture_loop_scope = "function"` (pytest 9.2.0
errors at configure without it; the deprecation's own prescription).
- `README.md` / `AGENTS.md` — the `/gateway` paragraph names `enabled`/`off`, the `#off`
id suffix, and the marker-aware warnings rule (marker-caused shapes warn nothing; the
same shape from a hand-deleted line still warns). Task 08's docs pass re-checks both
project-wide.
## Dependency maintenance (rule 8)
`uv pip list --outdated` was non-empty before this run's gate; updated on purpose:
`uv lock --upgrade` + `uv sync` — pydantic 2.14.2, pydantic-core 2.50.2, pytest 9.2.0,
ruff 0.17.3, websockets 17.4 (nodeenv/opentelemetry-api resolved by the same pass).
Now empty.
## Criteria evidence
- 3-marker fixture → three `enabled: false` rows, intact, `warnings == []`: the source
test above; the API arm asserts `block["disabled"] == 3`.
- Markers removed → warnings back: the pair above (one test pair, both directions).
- `gateway.disabled`: `3` marked / `0` clean capture / `null` stub (stub test asserts
`block["disabled"] is None`); the overview line prints nothing for `0` — pinned in
the JS test and observed on the live page (below).
- No `severity` key at any depth: the recursive walk passes, extended, unmodified in
meaning.
- Gate: `uv run pytest` 1877 passed / 17 skipped; coverage 98.86% (floor 94%;
`sources/gateway.py` 98%, `api.py` 99%, both >90%); ruff clean; pyright 0;
`node --check` clean; `uv pip list --outdated` empty; `bash .agents/validate.sh`
exit 0.
## Live verification (rule 10)
Dev server restarted (`./scripts/dev-server.sh restart`); `/api/health` ok for all
sources; the restarted server's live `/api/summary` gateway block serves the new key:
`{'host': 'gateway', 'listeners': 10, 'connections': 33, 'disabled': 0, 'live_in': 14,
'live_out': 14, 'map_targets': 18, 'warnings': 0}` — `disabled: 0` is the honest live
value (the real fleet carries no `#off` marker; the live config was not touched).
Real-browser pass signed in with `testpass`: the ingress line renders
`gateway 10 listeners · 33 connections · 15 live in · 15 live out` — the four figures
present, no `· 0 switched off` clause (the guard suppresses it at `0`, the
empty-state rule restated for the switch). Screenshot:
`data/logs/gateway-switch-04-overview-line.png`.
## Flags for the operator (bookkeeping, left to the driver)
1. `04_...md` now exists **identically in both** `todo/` and `complete/` (the `a1` run
copied it without removing the `todo/` copy; 01–03 follow the move convention).
Per the working protocol I did not touch `todo/`; the driver's move step should
remove the `todo/` copy.
2. `05_...md` also exists in `complete/` from the aborted `a1` run — **task 05's work
is not in the tree** (no `POST /api/gateway/route`, no `GATEWAY_ALLOW_WRITES`, no
`collectors.post_route`), and its `a1` transcript ends mid-work ("Now let me read
the code I'm touching"). Task 05 needs a clean re-run; its `todo/` copy is still
present, which is exactly what the driver looks for.
3. None of the phase-33 file moves or code is committed yet (`git status`: 01–03 moved,
04/05 in `complete/` untracked, all code changes uncommitted).
@@ -0,0 +1,10 @@
All changes are in the working tree, nothing staged. Task complete — final report:
**Phase 33 task 05 — done. `POST /api/gateway/route` is the app's second and last upstream write.**
- `config.py`: `GATEWAY_ALLOW_WRITES` (default **off**, NUT truthy rule) + `GATEWAY_WRITE_TIMEOUT_SECONDS` (35 s, outlives the agent's 30 s peer timeout); read budget untouched.
- `collectors.py`: `post_route` — per-dial `AgentTls`, revocation before any socket, no `Authorization` on the certificate path, redacted URLs, helper's object verbatim.
- `api.py`: the route with the ten numbered gates (1–3 middleware; 4 `DASH_READ_ONLY`; 5 flag; 6 `route:<address>` throttle + `Retry-After`; 7 hand-parsed body 422; 8 payload state 409 `{changed: false}`; 9 freshness 409; 10 `danger="DANGER"` for both verbs); one event per call, no diff/path/config text in any event; `/api/config` serves `gateway.writes_enabled`.
- Tests: surface test rewritten (two fleet writes, fails on a third), all ten gates parametrised with a dial spy, revoked-agent no-socket, no-diff event pin, danger/throttle/read-only/write-timeout pins, wire-id base spelling; `test_auth_api` walk + CSRF on the wire.
- Docs same commit: AGENTS.md invariant 1 now names two gated exceptions (plus the api/collectors/config bullets, gap 8 removed), README + `.env.example` env rows; `DASH_READ_ONLY` greps in both write paths.
- Dev-server curl checks: writes-off answers the `GATEWAY_ALLOW_WRITES` sentence; live 1.3.0 fleet answers the version gate first (correct order).
- `bash .agents/validate.sh` **exit 0**: 1915 passed / 17 skipped, coverage 98.72% (94 floor; `api.py` 99%, `collectors.py` 98%), ruff + pyright clean.
@@ -0,0 +1,70 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 11%]
........................................................................ [ 14%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 29%]
.......................................................................s [ 33%]
........................................................................ [ 37%]
........................................................................ [ 40%]
........................................................................ [ 44%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 63%]
........................................................................ [ 67%]
........................................................................ [ 70%]
........................................................................ [ 74%]
.............sssssssssssss.............................................. [ 78%]
........................................................................ [ 81%]
........................................................................ [ 85%]
........................................................................ [ 89%]
........................................................................ [ 93%]
........................................................................ [ 96%]
..sss....................................................... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/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.8-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 201 0 100%
src/dashboards/api.py 622 9 99% 229, 1273-1275, 1310-1313, 1379, 1387, 1436, 1439
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 244 4 98% 387, 391-392, 409
src/dashboards/config.py 128 0 100%
src/dashboards/debug.py 14 0 100%
src/dashboards/poller.py 165 3 98% 192-194, 279
src/dashboards/prom.py 112 4 96% 126, 163, 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/gateway.py 613 10 98% 339, 588, 601-602, 652, 785, 1089, 1140, 1207, 1569
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 4366 54 99%
Required test coverage of 94% reached. Total coverage: 98.76%
1915 passed, 17 skipped, 1 warning in 173.60s (0:02:53)
== 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,10 @@
Task 06 complete. Final report:
**33/06 — the switch obeys `gateway.writes_enabled`**
- **Two-arm switch** (`page-gateway.js`): the arm is `gateway.writes_enabled` read from `/api/config` (never inferred; a dead fetch leaves the safe arm). Writes-off: `aria-disabled="true"`, `cursor: not-allowed`, no listener, title = the API's refusal byte-for-byte. Writes-on: focusable `role="switch"`, `cursor: pointer`, one click listener, a title naming the config-file edit → validator → reload. Only the switch's own `aria-checked` may be optimistic; the row's word/class/spine stay the payload's.
- **Flow**: click → server `dry_run: true` (Gate 10 now exempts it; it asks nothing, writes nothing, and writes no `route-*` event) → dialog prints the diff verbatim (`+`/`−` tinted from the existence pair, `--gate-on`/`--gate-off`) → typed `DANGER` (wrong word sends nothing) → one send, `aria-busy`, no retry loop. `applied: true` flips the switch early + `refresh()`; `applied: false` prints the helper's word and flips nothing.
- **Pins**: the spec's four named tests added (`…wired_only_when_the_deployment_enables_writes`, `…preview_then_the_word_then_sends_once`, `…painted_from_the_answer_not_the_click`, `…sentence_matches_the_routes_refusal` — the last reads both files via `ast`); hover allowed on the live arm only; `routeInConfig` pin moved to `conn.enabled !== false` with `connections.enabled` in `GATEWAY_PAGE_FIELDS`; dialog/diff pins added.
- **Gate**: `.agents/validate.sh` green — 1919 passed / 17 skipped, coverage 98.72% (floor 94), ruff + pyright clean, `node --check` clean.
- **Browser** (Node Playwright): hermetic writes-on smoke (patched `post_route`, `marked` fixture) — live arm, dry-run diff, wrong word, `DANGER` applied, optimistic flip, refused arm; 360px viewport (no sideways scroll, dialog in view), focus in the confirm field, Esc closes and returns focus; hover tint verified. Live dev server (restarted, all sources ok): off arm exact sentence + `not-allowed`.
- **Docs**: README switch section + agent 1.4.0; AGENTS.md invariant 1 gains the dry-run clause. Screenshots in `data/logs/33-task06-*.png`.
@@ -0,0 +1,70 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 11%]
........................................................................ [ 14%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 29%]
........................................................................ [ 33%]
...s.................................................................... [ 37%]
........................................................................ [ 40%]
........................................................................ [ 44%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 63%]
........................................................................ [ 66%]
........................................................................ [ 70%]
........................................................................ [ 74%]
.................sssssssssssss.......................................... [ 78%]
........................................................................ [ 81%]
........................................................................ [ 85%]
........................................................................ [ 89%]
........................................................................ [ 92%]
........................................................................ [ 96%]
......sss....................................................... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/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.8-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 201 0 100%
src/dashboards/api.py 625 9 99% 229, 1273-1275, 1310-1313, 1382, 1390, 1446, 1449
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 244 5 98% 386-387, 391-392, 409
src/dashboards/config.py 128 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/gateway.py 613 10 98% 339, 588, 601-602, 652, 785, 1089, 1140, 1207, 1569
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 4369 56 99%
Required test coverage of 94% reached. Total coverage: 98.72%
1919 passed, 17 skipped, 1 warning in 171.78s (0:02:51)
== 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,13 @@
No interactive channel is available to confirm operator presence, so per the task protocol I stop after step 3 and report.
---
**Task 07 report — steps 1–3 complete; step 4 (live drill) not started, operator absence**
1. **Backups** — all four live configs + agent unit hashed to `/root/dashboards-33-backup-20261101-0950`; `diff -r` byte-identical.
2. **Rehearsal** — sandbox tree with real config copies: 3 `--apply` arms (haproxy server, nginx vhost, nginx map→sink) and the `map-default` refusal all behaved; checker-failure restore proven; sandbox left byte-identical.
3. **Install** — 1.4.0 + `--allow-writes` (installer diff: exactly one line), routectl helper/socket/template on the box; two live-kernel bugs found and fixed, both pinned in tests.
4. **Live-kernel fixes (repo + box)** — `Accept=yes` socket activation (systemd 257 rejected `Service=`); `serve()` reads fd 3 then fd 0 with `recv()`; refusals sent on the wire; ucred layout calibrated (this kernel delivers `{pid,uid,gid}`) with a root `setuid` probe so a matching gid on a foreign uid **cannot** open the gate; a `refused()` tuple-inversion that serialised refusals as JSON lists was caught by the live chain and fixed.
5. **Gates proven live** — true uid-994 client passes; `nobody` and uid-1000 get EACCES; root-with-matching-gid is refused by the peer gate; full 4-hop dry run (dashboard TLS dial → agent `POST /route` → socket → helper) returned the helper's verbatim diff (`#off iperf3`), nothing written.
6. **Post-dry-run state** — all four config hashes equal pre-drill, state sidecar empty, zero proxy reloads; full suite/Ruff/Pyright/`validate.sh` green on the final code.
7. **Step 4 blocked** — the off→on drill on a named route awaits an operator present to watch; the ingress is left byte-identical to its pre-work state.
@@ -0,0 +1 @@
error: compaction failed: Auto-compaction failed: Summarization failed: generation hit the token cap and the summary is incomplete; compaction failed: Auto-compaction…
@@ -0,0 +1,73 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 10%]
........................................................................ [ 14%]
........................................................................ [ 18%]
........................................................................ [ 21%]
........................................................................ [ 25%]
........................................................................ [ 29%]
........................................................................ [ 32%]
.........................s.............................................. [ 36%]
........................................................................ [ 40%]
........................................................................ [ 43%]
........................................................................ [ 47%]
........................................................................ [ 51%]
........................................................................ [ 54%]
........................................................................ [ 58%]
........................................................................ [ 62%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 73%]
.................................................sssssssssssss.......... [ 76%]
........................................................................ [ 80%]
........................................................................ [ 84%]
........................................................................ [ 87%]
........................................................................ [ 91%]
........................................................................ [ 95%]
......................................sss............................... [ 98%]
........................ [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/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.8-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 201 0 100%
src/dashboards/api.py 625 9 99% 229, 1273-1275, 1310-1313, 1382, 1390, 1446, 1449
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 244 5 98% 386-387, 391-392, 409
src/dashboards/config.py 128 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/gateway.py 613 10 98% 339, 588, 601-602, 652, 785, 1089, 1140, 1207, 1569
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 4369 56 99%
Required test coverage of 94% reached. Total coverage: 98.72%
1951 passed, 17 skipped, 1 warning in 193.21s (0:03: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 ==
advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose):
anyio 4.15.1 4.16.0 wheel
@@ -0,0 +1,10 @@
All green. Task 07 re-run is complete:
1. **Fixed:** anyio 4.15.1 → 4.16.0 (`uv lock` + `uv sync`; `uv pip list --outdated` now empty, rule 8); completed the two missing task-07 deliverables.
2. **Added** the opt-in live test (step 8): `test_the_switch_round_trip_on_one_named_route` — `live`-marked, gated by a separate `--allow-live-writes` flag (new `conftest.py` option), skips by default with a reason naming its route, restores in a `finally`; dials the app's own `collectors.post_route` and proves the round trip on the production read (dry-run diff, helper's `applied/validated/reloaded`, row `enabled` both ways, byte-identical haproxy section pre/post).
3. **Wrote** `data/logs/NOTES-33-switching.md`: first line = *all routes enabled*; before/after hashes identical (drill never wrote); guarded unit diff = exactly ` --allow-writes`; socket `0600 dashboards-agent:dashboards-agent`; three probe transcripts (`list` → `{"ok":true,"state":[]}`, foreign uid → EACCES, wire body gate); journal window (two activations, no reload).
4. **Decisions:** drill stopped at step 4 — (a) no operator said go in-session (the task's own stop condition) and (b) independently, the checker precondition cannot pass: no host `nginx`/`haproxy` binary on the box (podman container units), so a real `--apply` refuses at the checker and restores; the checker fix (container path or host binaries) is a task-08 decision, not made unilaterally.
5. **New finding (recorded, not fixed):** this box's systemd 257 build silently drops the helper unit's `ProtectSystem=read-only` (gperf bug — `off/yes/strict/full` parse clean); the socket gate is intact; candidate `full` + state-dir `ReadWritePaths` fix left for operator review with the unit diff.
6. **Gates:** `validate.sh` exit 0 — **1951 passed, 18 skipped**, coverage **98.72%** (floor 94%), Ruff + Pyright clean, JS syntax + hygiene pass; dev server: 10 sources `ok`.
7. **Live read-only:** `pytest -m live --live` → 13 passed, 1 skipped (the round-trip, skip reason names `haproxy:5201->web.reeselink.com:5201`).
8. **Next pending:** task 08 (docs + live verify) — the drill itself (needs an operator's go plus the checker fix) and the `ProtectSystem` decision.
@@ -0,0 +1,71 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 10%]
........................................................................ [ 14%]
........................................................................ [ 18%]
........................................................................ [ 21%]
........................................................................ [ 25%]
........................................................................ [ 29%]
........................................................................ [ 32%]
.........................s.............................................. [ 36%]
........................................................................ [ 40%]
........................................................................ [ 43%]
........................................................................ [ 47%]
........................................................................ [ 51%]
........................................................................ [ 54%]
........................................................................ [ 58%]
........................................................................ [ 62%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 73%]
.................................................ssssssssssssss......... [ 76%]
........................................................................ [ 80%]
........................................................................ [ 84%]
........................................................................ [ 87%]
........................................................................ [ 91%]
........................................................................ [ 95%]
.......................................sss.............................. [ 98%]
......................... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/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.8-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 201 0 100%
src/dashboards/api.py 625 9 99% 229, 1273-1275, 1310-1313, 1382, 1390, 1446, 1449
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 244 4 98% 387, 391-392, 409
src/dashboards/config.py 128 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/gateway.py 613 10 98% 339, 588, 601-602, 652, 785, 1089, 1140, 1207, 1569
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 4369 55 99%
Required test coverage of 94% reached. Total coverage: 98.74%
1951 passed, 18 skipped, 1 warning in 189.82s (0:03:09)
== 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,10 @@
All green. Final report:
**Task 08 complete — docs, browser pass, full gate.**
- **Docs**: `AGENTS.md` (invariant 1's two gated writes, 1.4.0 agent + marker grammar + no-retry rule, routectl deploy surface + units + byte-identical agent unit, `sources/gateway.py` `disabled`/merge/warnings + `/api/summary`, `api.py` gate order + audit events, `post_route` revoke-before-socket, fleet record, UI conventions, gap 8) and `README.md` (Route switching section: Enabling it + undo-everything, Switching a route with the three recovery commands and the deliberate-outage sentence, host-agent write, table row, two config rows, stale "unwired" paragraph fixed, orphaned UPS text restored) — plus `GATEWAY_ALLOW_WRITES` declared (never assigned) in both env templates, pinned by the new `tests/test_auth_docs.py` `WRITE_GATES` test.
- **Browser pass**: `33-switching.cjs` **45/45 checks, exit 0** — both arms, `--gate-off`/`--gate-on` equality end-to-end in dark + light (on-half distinct from `--warn`/`--fail`), paint follows the payload never the click, `applied: false` arm, Escape, reduced-motion still, 360 px zero sideways scroll; 8 screenshots + `33-switching.out`.
- **Mobile audit**: `27-task02-mobile-audit.cjs` re-run → **exit 0, 371 PASS, 0 FAIL, 0 finding** (`33-task08-mobile-audit.out`).
- **Gate**: `validate.sh` **exit 0** (1953 passed, coverage 98.76 % ≥ 94, ruff clean incl. `deploy`, pyright 0, outdated empty); plain `uv run pytest` green; live suite **13 passed + the write round-trip skipped by design** (reason names the route it would touch).
- **Status checks**: dev server (writes off) 10/10 ok unchanged; smoke server (flag on, fake agent) 10/10 ok honest (real mTLS dials — the script now seeds the dev DB's `certs.*` rows read-only, since a fresh DB mints a CA the agents refuse) and a curl write round-trips: dry-run diff → `applied/validated/reloaded` → next payload's row `#off`/`enabled: false`.
- **Live call (conservative, no live changes)**: no real `DANGER` sent. The dry-run rehearsal stands from task 07; the phase's own decision leaves the drill to the operator, and this fleet's proxies run in podman containers so the host lacks the `nginx`/`haproxy` binary the helper's checker needs — a live drill here would refuse by construction (recorded in `NOTES-33-switching.md`). Read-only live verification is green.
@@ -0,0 +1,71 @@
== uv run pytest --cov (hermetic: no network, live tests skip) ==
........................................................................ [ 3%]
........................................................................ [ 7%]
........................................................................ [ 10%]
........................................................................ [ 14%]
........................................................................ [ 18%]
........................................................................ [ 21%]
........................................................................ [ 25%]
........................................................................ [ 29%]
........................................................................ [ 32%]
.........................s.............................................. [ 36%]
........................................................................ [ 40%]
........................................................................ [ 43%]
........................................................................ [ 47%]
........................................................................ [ 51%]
........................................................................ [ 54%]
........................................................................ [ 58%]
........................................................................ [ 62%]
........................................................................ [ 65%]
........................................................................ [ 69%]
........................................................................ [ 73%]
...................................................ssssssssssssss....... [ 76%]
........................................................................ [ 80%]
........................................................................ [ 84%]
........................................................................ [ 87%]
........................................................................ [ 91%]
........................................................................ [ 94%]
.........................................sss............................ [ 98%]
........................... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/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.8-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 201 0 100%
src/dashboards/api.py 625 9 99% 229, 1273-1275, 1310-1313, 1382, 1390, 1446, 1449
src/dashboards/app.py 190 0 100%
src/dashboards/auth.py 265 0 100%
src/dashboards/certs.py 292 0 100%
src/dashboards/collectors.py 244 5 98% 386-387, 391-392, 409
src/dashboards/config.py 128 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/gateway.py 613 10 98% 339, 588, 601-602, 652, 785, 1089, 1140, 1207, 1569
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 4369 56 99%
Required test coverage of 94% reached. Total coverage: 98.72%
1953 passed, 18 skipped, 1 warning in 191.57s (0:03:11)
== 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 ==