Update the continuation TODO for phases 4-6

Items 6, 2 and 10 move to Done. Remaining is the phone chunk (1/4/3), then
gating (8), then curtains (5) and the black screen (13).

Adds three traps this session paid for: vm run's exit code fails a gate open,
_state.py's UTC key is load-bearing across repos and must not be "fixed", and
ruff's autofix will silently gut a function whose only effect is printing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
This commit is contained in:
Krzysztof Rudnicki 2026-08-27 20:49:55 +02:00
parent 97d32c803d
commit c43af4edb7

View File

@ -2,7 +2,7 @@
REMOVE ME AFTER FINISH
Phases 0–3b are committed (`4286fcb`..`b3e8c23`). This file is the entry point
Phases 0–6 are committed (`4286fcb`..`97d32c8`). This file is the entry point
for the rest. **Full plan, with every measured finding:**
`~/.claude/plans/wake-alarm-still-does-witty-toucan.md` — read it before
starting; this file deliberately does not duplicate it.
@ -11,32 +11,21 @@ starting; this file deliberately does not duplicate it.
| Item | State |
|---|---|
| 6 audio (HDMI silent) | `_reactivate.py` retries card-profile activation. **Not yet wired into `WakeAlarm`** — see below. |
| 6 audio (HDMI silent) | **Done.** `_reactivate` wired into `WakeAlarm`; verified on the host — the HDMI sink appeared and became default. |
| 7 speakers | `DOCS-speaker-recommendation.md`. Prose only, done. |
| 9 refactor | `_alarm.py` 478→250. 12 new modules; see `DOCS-architecture.md`. |
| 11 failsafe | `FAILSAFE_SILENCE_SECONDS=300`, audio only. Escalation re-tuned to fit inside it. |
| 12 logging | JSONL + cross-process dedupe + derived correlation id. |
| 14 fans | Gone: code, script, sudoers entry, installed state. |
| 14 fans | Gone: code, script, sudoers entry, installed state. Three stale doc/message references cleaned up in phase 6. |
| 2 power button | **Done.** `_inhibit.py` (logind block, self-terminating, stale cleanup by pid via `/proc`) + `_alarm_session.py` fire-time record, re-fired by `_trigger` outside the catch-up window. |
| 10 vmbox | **Done.** `scripts/vmbox/harness.sh` + `DOCS-vmbox.md`. 8 cases, fails closed. Found 2 live bugs the host could not reach. |
## Remaining, in order
1. **Wire `_reactivate` into `WakeAlarm`** (finishes item 6). It exists,
is tested, and is currently called by nothing. Start it next to
`_activate_all_outputs()` in `__init__`, pass `self._stop_beep` as `stop`.
~20 lines. Do this first — every other item assumes the alarm is audible.
2. **Item 2, power button.** `systemd-inhibit
--what=handle-power-key:shutdown:idle --mode=block` around the alarm, in a
new `_inhibit.py`, released in `on_close()`. Verified viable:
`PowerKeyIgnoreInhibited=no` is in force, so no `logind.conf` edit is
needed. Then the real defence — a **fire-time** session record
(`wake_state.json` is only written on *dismissal* today, so a hard
power-off leaves no trace), and `_trigger` re-fires on an unfinished
session ignoring the catch-up window. Fix the UTC/local key mismatch in
`_state.py` in the same commit.
3. **Item 10, vmbox harness.** `scripts/vmbox/harness.sh` + `DOCS-vmbox.md`.
4. **Items 4/3/1, phone.** Biggest chunk by far. Own session.
5. **Item 8, mutual gating.** Needs 4 done first.
6. **Item 5 curtains**, then **item 13 black screen** (different repo,
1. **Items 4/3/1, phone.** Biggest chunk by far. Own session. Everything here
is new — the app has zero alarm capability today, only a time picker.
2. **Item 8, mutual gating.** Needs 1 done first.
3. **Item 5 curtains**, then **item 13 black screen** (different repo,
host-only).
## Decisions already made — do not re-litigate
@ -61,11 +50,11 @@ starting; this file deliberately does not duplicate it.
## Traps that cost time this session
- **The 250-line gate fires on *staged* files.** Touching a file means
splitting it in the same commit. 5 source + several test files are still
over (measured 2026-08-27): `test_sync.py` 386, `_sync.py` 371,
`test_smart_plug.py` 351, `_sinks.py` 341, `_alarm_view.py` 329,
`_escape_flow.py` 317, `test_state.py` 314, `test_trigger.py` 252.
Budget for it.
splitting it in the same commit. Still over (re-measured 2026-08-27, after
phase 6): `test_sync.py` 386, `_sync.py` 371, `test_smart_plug.py` 351,
`_sinks.py` 341, `_alarm_view.py` 329, `_escape_flow.py` 317,
`test_state.py` 314. Budget for it. `test_trigger.py` is now split and
under.
- **`morning_routine/_orchestrator.py:39` hardcodes `"wake_alarm._alarm"`**
(different repo, `~/testsAndMisc`). Any module-path change updates it in the
*same* commit. This exact omission caused a month-long silent outage. Keep
@ -85,11 +74,24 @@ starting; this file deliberately does not duplicate it.
of `_beep_soft/_medium/_loud` and waits for it will spin forever on the real
audio stack and hang the whole suite instead of failing. Patch all three.
- Run tests with `/usr/bin/python3`, not the dev venv (no `gatelock` there).
- **`vm run` does not propagate the guest command's exit code** — it reports
the VM's verdict. A gate built on it fails *open*. See `DOCS-vmbox.md`.
- **`_state.py`'s UTC `_today_str()` must NOT be flipped to local**, despite
what the plan says. `screen_locker/_wake_state.py` mirrors the helper and
reads the same `wake_state.json`; flipping it loses an earned workout skip
for any dismissal between 00:00 and 02:00 local. `_alarm_session.py` shows
the way around it: carry an aware `fired_at`, compare elapsed time, no date
key at all.
- **Ruff's autofix will gut a function** whose results are only printed —
it stripped both the assignments and the `print` from a vmbox check, and
every linter stayed green. Grep the file for its output call after any
autofix.
## Verify
```
/usr/bin/python3 -m pytest wake_alarm/tests/ -q # 404 pass, 100% cov
/usr/bin/python3 -m pytest wake_alarm/tests/ -q # 455 pass, 100% cov
bash scripts/vmbox/harness.sh # 8/8 cases, exit 0
pre-commit run --files <changed>
/usr/bin/python3 -m wake_alarm._trigger --dry-run
/usr/bin/python3 -m wake_alarm._alarm --demo # real window; ask first