Bathroom misroute — 4 hodiny diagnostiky a fix v1.8

Včera večer ve 20:45 systém sám spustil radio do koupelny, kde nikdo nebyl. Žádný explicit příkaz, žádná scéna, žádný cube tap. Jen 46 sekund po tom, co AI coordinator nabídl „relax scénu", začalo z prázdné koupelny hrát Italian Classic Hits Sanremo.

Bug se zaregistroval jako medium severity (one-shot, dá se zastavit ručně), ale klíčové bylo, že se to mohlo opakovat při každém comfort_suggest. Tady je 4 hodiny diagnostiky shrnutých do 8 minut čtení.

Reprodukce

  1. AI coordinator vyhodnotí situation = comfort_suggest (level 4, evening + idle scene)
  2. TTS přečte: "Luďku, je večer a nic neběží. Chceš spustit relax scénu?"
  3. User neodpoví — ani cube, ani button, ani voice
  4. ~46 s po TTS completed: sh_audio_brain_v2 audio_dispatch event
  5. Speaker koupelna začne hrát radio (i když user není v koupelně)

State při bugu

sh_audio_current_mode = 'radio_bathroom'
sh_audio_active_speaker = 'Speaker koupelna'
sh_audio_playing = 'yes'
sh_audio_request = 'idle'           ← nikdo nepožádal o radio
sh_audio_final_request = 'idle'     ← ditto
sh_scene_active = 'idle'            ← scéna se neaktivovala
sh_user_intent = 'idle'             ← user neodpověděl
sh_user_intent_request = 'idle'     ← ditto
sh_ai_coord_last_action = 'comfort_suggest'  ← byla jen NABÍDKA
sh_audio_prev_mode = 'idle'         ← žádné prev radio (nebylo co resumovat)
sh_audio_prev_speaker = ''          ← ditto
sh_tts_resume_pending = 'idle'      ← resume nečeká
sh_tts_resume_target = 'idle'       ← ditto

Klíčový červený alarm: sh_audio_current_mode='radio_bathroom' ale sh_audio_prev_mode='idle'. Mode se zaseknul z ranní rutiny, nikdo ho neresetoval, a po TTS completion fallback logika ho použila jako „kam se vrátit".

Hypotézy které neplatily

H1 — audio_brain auto-execute na comfort_suggest. Hypotéza: brain možná auto-aktivuje "comfort scene" 30–60 s po suggestion bez čekání na user response. Ověřeno čtením sh_audio_brain_v2 — žádný takový handler tam není. Falsified.

H3 — sh_audio_active_speaker zaseklý z morning. Hypotéza: z ranní rutiny zůstal active_speaker='Speaker koupelna', audio_brain dispatch routes podle něj. Částečně true (active_speaker BYL zaseklý), ale dispatch nebyl problém — problém byl ve fallback logice resume_exec.

H4 — flow listener na ai_coord_last_action. Hypotéza: někde flow má trigger „sh_ai_coord_last_action changed + condition comfort_suggest → action play radio". Grep across všech flows nenašel žádný takový. Falsified.

Root cause (H2 winner)

sh_tts_resume_exec_v1 (id 009149d9-0e49-4881-b41d-502b52ca652c), který se volá automaticky po každém TTS completion, měl fallback handler:

// BEFORE v1.8
if (target === 'idle' && currentMode === 'radio_bathroom') {
  // Resume bathroom radio
  await dispatchAudio({mode: 'radio_bathroom', speaker: 'Speaker koupelna'});
}

Logika byla: „pokud TTS skončil, žádný explicit resume target nebyl, a current mode říká bathroom radio, vrať se k bathroom radio". Záměr: po krátké TTS notifikaci během sprchy resumovat radio, který hrál.

Co fallback nekontroloval: jestli to bathroom radio reálně hrálo PŘED TTS. sh_audio_current_mode se zaseknulo na 'radio_bathroom' z ranní rutiny v 03:35 a nikdo ho neresetoval. V 20:45 ten stav stále platil, i když speaker dávno mlčel.

Fix v1.8 — prev_playing guard

// AFTER v1.8
if (target === 'idle' && currentMode === 'radio_bathroom') {
  if (sh_audio_prev_playing !== 'yes') {
    logEvent({event: 'tts_resume_skip_no_prev_playing'});
    return;
  }
  // Resume bathroom radio
  await dispatchAudio({mode: 'radio_bathroom', speaker: 'Speaker koupelna'});
}

Podmínka navíc: sh_audio_prev_playing === 'yes'. Tato proměnná se nastavuje na 'yes' jen když TTS orchestrator detekuje aktivní audio playback PŘED svým spuštěním. Pokud nic nehrálo (jen TTS), prev_playing zůstává 'no' a resume se neprovede.

Test (25.4. noc)

Po deploy v1.8 jsem manuálně reprodukoval podmínky bugu:

  • Reset všech audio state vars do clean
  • Setup: sh_audio_current_mode='radio_bathroom', sh_audio_prev_playing='no' (klíčový rozdíl)
  • Trigger TTS přes sh_tts_orchestrator_v1
  • Wait 60 s
  • Check: sh_audio_request stále 'idle', žádný dispatch event

Test passed. Resume_exec correctly skipped, no radio fired. Log entry tts_resume_skip_no_prev_playing zaznamenán.

Long-term watch

Fix v1.8 řeší symptom, ne příčinu zaseklého sh_audio_current_mode. Pokud znovu uvidíme stuck mode, bude potřeba přidat reset do bathroom radio stop logic — když speaker stop, set mode='idle'. Aktuálně mode drží svou poslední hodnotu, což je v zásadě dirty state.

Backlog item: tools/audit_audio_state_consistency.py — periodický check, jestli current_mode odpovídá reálnému speaker_playing stavu. Brain Guardian extension.

Co se naučilo

Fallback logika musí explicitně ověřit, že podmínka, na kterou fallback reaguje, je aktuální, ne jen recent. State proměnné se umí zaseknout v ghost hodnotách, které vypadají platně, ale neodpovídají reálnému světu.

Tohle není první ghost state bug. Stuck sh_priority_active='tts' incident byl podobný — proměnná držela hodnotu z incomplete pipeline a blokovala následující operations. Brain Guardian validator vrstva (V1 LIVE od 26.4.) je odpovědí na celou tuhle kategorii bugů — periodický cross-check state vs. reality.

4 hodiny diagnostiky → 4 řádky kódu. Typický poměr u state-driven bugů. Hypothesis ladder (H1 → H2 → H3 → H4) je nezbytný — bez systematické eliminace bych skončil u quick fix který by jen maskoval symptom.