diff --git a/docs/qa.md b/docs/qa.md index 042aecb..f9073f4 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -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