**Phase 05 final pass — all criteria met; whole project 100 % green.**
Verified (live upsd + isolated simulator):
- Real `apc`/`cyberpower`: Charge/Load/Runtime are the card's three largest elements (38.4 px vs 15.2 px next), coloured only by API `severity` — live right now 100 % ok / 79.0 % **warn** / 5m **fail**; glyph fill = charge (100/100 viewBox units, `--ok` token), `<title>` "apc: Charge 100 percent, on line"; SIMULATED hidden; battery °C `n/a` uncoloured; every button disabled with its reason; `confirm_text: DANGER` on power-cutting commands; **0 non-GET requests** → `data/logs/ups-verify-live-{card,full}.png`.
- Simulator on `:8091` (own `/tmp` DB, killed after): caution dialog → Cancel, dangerous dialog → Run disabled until `DANGER` → Cancel, **0 POSTs**; one deliberate quick test *to the sim only* flipped the card to `OB` (glyph `stroke-dasharray="7 5"`, "on battery", health "discharging, on battery") then `OL CHRG` (bolt) then `OL` → `ups-verify-sim-{dialog-caution,dialog-danger,onbattery-button,onbattery-page,charging-button}.png`; glyph matrix `ups-glyph-*.png` (100/mid/low/unknown/empty/unrated, dark/light/200 %) present; `grep -c "function battery"` = 1.
Defects found in earlier work and fixed:
1. **`poller.py` could stop polling in silence** — 4 of 6 dev-server sources had frozen `attempts` (ages 12–38 min) while `/api/health` still said `ok: true`: the store-write half of `run_once` was outside the `try`, and a dead task's exception is never retrieved. Now `_commit()` moves `last_ok` only after the write lands, `_store_refused()` reports it (`ok:false`, real reason, one `store-error` event + one `recording` on recovery, payload kept), and `_loop()` catches anything that escapes and keeps polling — 4 new tests in `tests/test_poller.py`.
2. **`nut_sim.py` (modified by this phase) sat at 83 % coverage**; its whole command path was untested. Added tests (every advertised command implemented, unknown → `ValueError`, alarms → card + `assess()` fail, `NUT_SIMULATE` truthy rule) and fixed two model lies: `beeper.*` answered OK while changing nothing (now publishes `ups.beeper.status` enabled/muted/disabled) and a finished self-test never wrote `ups.test.result` (aborted writes none). → 100 %.
3. `nut_sim.from_env()` was dead code duplicating `app._truthy`; `app.py` now builds the simulator through it, so the enable gate lives once.
Tests / lint (exact): `bash .agents/validate.sh` → **EXIT=0** — `uv run pytest --cov --cov-fail-under=94` **543 passed, 8 skipped, coverage 97.91 %**; `uv run ruff check src tests agent` clean; `uv run pyright` 0 errors; `node --check` clean; repo hygiene clean. `uv run pytest -m live --live` **with `.env.dev` sourced → 8 passed** (one earlier run: 2 transient `9121/snapshot timed out` against the 4 s `HOST_TIMEOUT`; three later runs 8/8). `uv pip list --outdated` empty. Dev server restarted on the final code: `health ok`, 6/6 sources fresh.
Docs: AGENTS.md — invariant 4 extended ("nothing may stop polling in silence" + store-refusal semantics), `poller.py`/`nut_sim.py` responsibility lines, 533→543 and 96.9 %→97.9 %, "~97 %"→"~98 %". README.md — poller bullet gained the same guarantee, Testing gained "source `.env.dev` before `-m live --live`" (bare runs skip 7 of 8 live tests, which reads like a pass), Battery-test section gained the `deploy/nut-sudoers` install line and the read-before-write warning.
Notes/deviations: no live instant command was ever sent to real hardware (the four gates leave them disabled — AGENTS gap 1 unchanged); the on-battery proof was driven through the real button against `NUT_SIMULATE=1`, which is stronger than the NOTES hand-driven states. `NOTES-verify.md` under `todo/` left untouched per instructions. Next phase: **none** — 05 is the last in the PLAN.md roadmap.
76 lines
2.6 KiB
Plaintext
76 lines
2.6 KiB
Plaintext
# Verbatim upsd reply (LIST UPS / LIST VAR / LIST CMD), recorded by
|
|
# ./scripts/capture-fixtures.sh — one `VAR`/`CMD` line per protocol line.
|
|
# Volatile values (ups.load, battery.runtime, the voltages) move every poll,
|
|
# so a diff here on re-capture is expected; the contract asserts shape, units
|
|
# and which variables the drivers omit, never those numbers.
|
|
UPS apc "APC Back-UPS RS 1500MS (USB)"
|
|
VAR apc battery.charge "100"
|
|
VAR apc battery.charge.low "10"
|
|
VAR apc battery.charge.warning "50"
|
|
VAR apc battery.date "2001/09/25"
|
|
VAR apc battery.mfr.date "2021/06/24"
|
|
VAR apc battery.runtime "648"
|
|
VAR apc battery.runtime.low "120"
|
|
VAR apc battery.type "PbAc"
|
|
VAR apc battery.voltage "27.3"
|
|
VAR apc battery.voltage.nominal "24.0"
|
|
VAR apc device.mfr "American Power Conversion"
|
|
VAR apc device.model "Back-UPS RS 1500MS"
|
|
VAR apc device.serial "SCRUBBEDSERIAL1 "
|
|
VAR apc device.type "ups"
|
|
VAR apc driver.debug "0"
|
|
VAR apc driver.flag.allow_killpower "0"
|
|
VAR apc driver.name "usbhid-ups"
|
|
VAR apc driver.parameter.interrupt_pipe_no_events_tolerance "-1"
|
|
VAR apc driver.parameter.pollfreq "30"
|
|
VAR apc driver.parameter.pollinterval "2"
|
|
VAR apc driver.parameter.port "auto"
|
|
VAR apc driver.parameter.productid "0002"
|
|
VAR apc driver.parameter.synchronous "auto"
|
|
VAR apc driver.parameter.vendorid "051d"
|
|
VAR apc driver.state "quiet"
|
|
VAR apc driver.version "2.8.5"
|
|
VAR apc driver.version.data "APC HID 0.101"
|
|
VAR apc driver.version.internal "0.71"
|
|
VAR apc driver.version.usb "libusb-1.0.30 (API: 0x0100010C)"
|
|
VAR apc input.sensitivity "medium"
|
|
VAR apc input.transfer.high "144"
|
|
VAR apc input.transfer.low "88"
|
|
VAR apc input.voltage "123.0"
|
|
VAR apc input.voltage.nominal "120"
|
|
VAR apc ups.beeper.status "disabled"
|
|
VAR apc ups.delay.shutdown "20"
|
|
VAR apc ups.firmware "952.e5 .D"
|
|
VAR apc ups.firmware.aux "e5 "
|
|
VAR apc ups.load "48"
|
|
VAR apc ups.mfr "American Power Conversion"
|
|
VAR apc ups.mfr.date "2021/01/08"
|
|
VAR apc ups.model "Back-UPS RS 1500MS"
|
|
VAR apc ups.productid "0002"
|
|
VAR apc ups.realpower.nominal "900"
|
|
VAR apc ups.serial "SCRUBBEDSERIAL1 "
|
|
VAR apc ups.status "OL"
|
|
VAR apc ups.test.result "No test initiated"
|
|
VAR apc ups.timer.reboot "0"
|
|
VAR apc ups.timer.shutdown "-1"
|
|
VAR apc ups.vendorid "051d"
|
|
CMD apc beeper.disable
|
|
CMD apc beeper.enable
|
|
CMD apc beeper.mute
|
|
CMD apc beeper.off
|
|
CMD apc beeper.on
|
|
CMD apc driver.killpower
|
|
CMD apc driver.reload
|
|
CMD apc driver.reload-or-error
|
|
CMD apc driver.reload-or-exit
|
|
CMD apc load.off
|
|
CMD apc load.off.delay
|
|
CMD apc shutdown.default
|
|
CMD apc shutdown.reboot
|
|
CMD apc shutdown.stop
|
|
CMD apc test.battery.start.deep
|
|
CMD apc test.battery.start.quick
|
|
CMD apc test.battery.stop
|
|
CMD apc test.panel.start
|
|
CMD apc test.panel.stop
|