qa plan: step 2 passes headless, and the sev4 that names nothing (V-287)

Chrome takes a fake microphone, so the browser half of push-to-talk runs
without a person. getUserMedia, MediaRecorder, the webm decode and the
resample all pass. The button is at /, not /dash, which this step had wrong.
The on-screen transcript shows + for every space: QueryEscape decoded with
decodeURIComponent. Filed as 533.

A real sev4 reached telegram with presence away. It named no service, which
is 534: nudgeValues fills {service} from an exact key mavpoll stopped writing
when per-monitor facts landed, so every named variant is rejected as
unfillable and the one nameless variant always wins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011x5DgnExQ5XZy8TZPs5bot
This commit is contained in:
2026-08-04 23:07:23 +04:00
parent 7695620a96
commit 2e64c8ce94
+45 -1
View File
@@ -124,6 +124,28 @@ five at `predicate`, none inert:
So the honest way to run step 8 is to wait three hours after the last `water`
fact, or to write one antedated. Do not read the water rule's silence as a defect.
**The sev4 telegram reach works** (04-08-2026). Resuming a paused kuma monitor
for paperless, which is genuinely down, put a real `service_down` through the
whole path with presence away:
```
23:03 voicesink: no live voice session for service_down, falling through to away channels
/notifications: 19:03 | service_down | telegram | pending | Сервис перестал отвечать.
04.08 23:03 | nudge | service_down | telegram | sent | 23:03
```
`ChannelsFor(Sev4, Away)` returned telegram, the send succeeded, and the row
holds at `pending` because sev4 repeats until acked. The 15:51 row shows the
same rule reaching `acted` earlier, so the ack path works too.
The body was `Сервис перестал отвечать.`, which names no service. That is a bug
and it is deterministic, filed as **534**. `nudgeValues` fills `{service}` from
`State.Fact("service_down")`, an exact key mavpoll stopped writing when
per-monitor facts landed. Nine of the ten templates carry `{service}`, so all
nine are rejected as unfillable. The one nameless variant is left as the only
usable one, every time. The stub and LLM phrasers both call `loop.DownServices`
and get it right. The template path is the one that runs.
**Presence itself has a real defect, filed as 532.** `SavePresenceState` has no
caller outside tests, so the singleton row is never written. The gate is fine,
because it reads the bucket `GatherState` computes in memory each tick. Two
@@ -157,7 +179,29 @@ turns look misaligned when they are not.
HTTP 200, `audio/l16;rate=16000;channels=1`, and real speech back. `ru_query`
answered `на 04.08.2026 ничего нет.` in 3.82s of audio at RMS 3865, `ru_fact`
answered `отметила: water = выпил`, `ru_reminder` answered `хорошо, напомню.`
at `intent=reminder`. Only `getUserMedia` and the button are still unproven.
at `intent=reminder`.
**Passes in the browser too** (04-08-2026), and it needed no person. Headless
Chrome takes a fake microphone, so the whole browser half runs unattended:
```sh
chrome --headless=new --remote-debugging-port=9333 --remote-allow-origins='*' \
--use-fake-device-for-media-stream --use-fake-ui-for-media-stream \
--use-file-for-fake-audio-capture=cmd/mavsttd/testdata/ru_query.wav%noloop
```
Then drive it over the debug protocol: click `#btn`, wait, click again, read
`#status` and `#log`. That covers `getUserMedia`, `MediaRecorder`, the webm
decode and the hand-written resample to 16k Int16. It logged
`sending 188160 bytes`, which is 5.88s at 16k mono, and got the reply back.
**The button is on `/`, not `/dash`.** `handleVoice` serves it at the root
(`main.go:332`). `/dash` is the presence and fact dashboard and carries no
`#btn`. This step said `/dash` until 04-08-2026.
One defect fell out, filed as **533**. The reply logged as
`на+04.08.2026+ничего+нет.` The header is escaped with `url.QueryEscape`,
which writes a space as `+`, then decoded with `decodeURIComponent`, which
leaves `+` alone. Transcript only, the audio is fine.
3. Say `тихий режим`. Expect `тихий режим включён. буду реже напоминать.` **Passes.**
4. Say `выключи тихий режим`. Expect `тихий режим выключен.` Negation must win. **Passes.**
5. Say `в комнате тихо`. Quiet mode must NOT flip. Confirm on `/history` that no