Sandbox 24/7 + kiosk Phase E + cache retry — den úklidu

Den, kdy se RPi/HAOS přestal hrát na servisního pomocníka a stal se z něj plnohodnotná testovací brána mezi vývojem a produkcí. Tři vrstvy zároveň: Python sandbox runner běžící 24/7, RPi kiosek s cache-first fallback chainem a dashboard cache pipeline, která už netrucuje při občasném 417 z Athom relay.

Žádný Homey write, žádný edit dashboardů na HAOS, žádné device renames. Tři gatey současně — SANDBOX-FIRST, AUTO-REGISTRY, SAFE_REPAIR_MODE — drží celý postup v sandboxu, dokud se nepotvrdí, že to nic nerozbije.

Node.js do HAOS bez bolesti

První ranní zjištění: HA SSH addon na RPi nemá Node. Sandbox dostane Node až do persistentní vrstvy core_ssh apks config — tj. Supervisor ho automaticky reinstaluje při každém startu addonu, takže addon rebuild ho neodfoukne.

# Before
apks: ["python3"]
# After (LIVE, persistent přes Supervisor)
apks: ["python3", "nodejs", "npm"]

Verifikace: node -v → v24.14.1, npm -v → 11.11.0, smoke test čte všech 6 snapshot fixtures (devices/variables/zones/scripts_index/ sh_dashboard_data/sh_device_map) bez chyby. Po addon restartu Node přežil → persistence potvrzena.

Sandbox emulator — Phase B a C

Vrstva Node mocku Homey světa: homey_mock.js s Homey.logic, Homey.devices, Homey.flow, plus Homey.scripts.runScript() a Homey.apps.restart() jako schválně vyhazující funkce (zrcadlí reálné sandbox omezení v Homey). fetch() disabled, dokud test neposkytne explicit mock.

Phase B postavila 6 testů a 4 replay scénáře, Phase C přidala 10 dalších: T1-T16 pokrývá dashboard schemu, bathroom vacancy, Prague timezone, light router guards, roleta fallback, cron schema validation, audio vacancy, motion+presence off, morning alarm variants, anti-UTC regression, sleep window guard, kitchen scenes, dynamic temperature, dashboard cache fallback, cron invalid detection, HomeyScript crash resilience.

[PASS] T1_dashboard_schema           — 26 keys present, 0 missing
[PASS] T2_bathroom_vacancy           — proposed light_off + audio_stop
[PASS] T3_morning_prague_date        — 4/4 Prague vs UTC correct
[PASS] T4_light_router_guards        — 8/8 guards enforced
[PASS] T5_device_map_roleta_fallback — sh_device_map.BLIND_DINING resolves
[PASS] T6_cron_schema                — 83 cron cards, all valid
... 14 more PASS
PASS 20/20  FAIL 0

Phase D — sandbox 24/7 v HA automation

Sandbox-side testy běží jednou. Pravidelný runner za nás 24/7 čekal — shell_command volaný HA automation každou celou hodinu. První pokus selhal úsměvným způsobem.

2026-05-14 07:00:00.341 ERROR (MainThread)
  [shell_command] Error running command:
  `node /config/smart-home/sandbox/runner/sandbox_runner.js`,
  return code: 127

Exit 127 = node: command not found. HA Core kontejner Node nemá — naše Node persistence byla v core_ssh addonu, ne v hlavním HA Core. Dvě izolované docker vrstvy. Pivot: runner přepsán do Pythonu (HA Core má Python 3.12 nativně), Node sandbox vrstva zůstává jako vývojová.

F1-A naprogramoval Python orchestrátor pure-stdlib, mirror 16-test matrix + replay JSON validator + 8-step gate logic (snapshot → tests → replay → regression → rollback → user_go). F1-B přepojil HA shell_command z node na python3. ha core check PASS, restart, čekání na :00 cron, runs přeskočilo z 4 na 5 v 06:00:00.956Z přesně na hraně minuty:

[runner_py] runs=5 tests=16/16 replay=OK
            blockers=0 gate=DEPLOY_GATE_OPEN

Gate semantika: DEPLOY_GATE_OPEN znamená „všechny technické checky prošly", ale stále vyžaduje user_go: REQUIRED per change. Runner nikdy sám nezasahuje do live Homey. Jen verdict v markdown reportu.

Auto-registry — gate proti tichým novým entitám

Kdykoliv v systému přibude nová scéna, automatizace, Flow, HomeyScript, dashboard widget, modul, časový režim, tlačítko, gesto nebo trigger, runner ji detekuje při diffu vs. baseline registry a označí jako BLOCKED_UNTIL_TEST_EXISTS s návrhem názvu testu i replay scénáře.

Baseline registry teď drží 8 scenes + 132 scripts + 232 advanced_flows = 372 entit. Když přibude 133. script, runner pošle do MD reportu řádek typu „scripts: sh_new_thing → propose tests_py/test_sh_new_thing.py". Live deploy bez registry změny zablokovaný.

Dashboard cache retry — fix přes weekend

Stranou samotného sandboxu jeden konkrétní pipeline bug: dashboard cache script homey_dashboard_cache.py občas vrátil exit 1 v cca 5 % cron firings kvůli transientním errors z Athom cloud relay (vars fetch status=417 nebo connection drop). Failures byly vždy přechodné — následující call (do 60 s) uspěl. Data loss = 0, jen šum v HA logu.

Fix v2.1: homey_get rozdělen na _homey_get_once + retry wrapper. Transient retry na {0, 417, 502, 503, 504} s backoff 0.5 s + 1.5 s, max 3 attempts. Permanent statusy (401, 404, …) raise immediately bez retry.

RETRY_STATUSES = {0, 417, 502, 503, 504}
RETRY_BACKOFFS = (0.5, 1.5)  # 3 total attempts

Sandbox test T17 ověřil 5 case matrix (200 first try / 417+200 / 0+0+200 / 401 immediate raise / 417×3 exhausts) — 5/5 PASS. Po deploy dnes ráno error rate spadl z ~5 % na 1.8 % a HA log už neukazuje každohodinový červený řádek.

Kiosk Phase E — RPi-only rebuild

Kiosek (RPi 192.168.1.122 s 1024×600 WaveShare displayem) běžel HTML z 11. 5. v 19:25 — verze, která nikdy neznala dashboard cache. Když HAOS-side cache pipeline začala produkovat čerstvý JSON, kiosek o tom neměl jak vědět. Pořád polloval direct přes server.py proxy na Homey REST.

Pravidlo bylo přísné: NEČTI NIC Z PC. Phase E patch se musí znovu vytvořit přímo ze samotného kiosk HTML, ne kopírovat z PC dashboards/. Důvod: PC nemusí být online a má staré verze.

Workflow:

  1. SSH na kiosek, pull /home/smart/dashboard/index.html + server.py na HAOS staging dir.
  2. Binary-mode Python patcher zachovává CRLF line endings (kiosk file natively CRLF) — první pokus s LF-only způsobil false 29 822-line diff místo skutečných 112 řádků.
  3. Patch HTML: nahradit 7-řádkové fetchDashboardData() za 60-řádkovou verzi se 3-stage chainem cache→Homey REST→localStorage. Přidat #sourceBadge CSS + DOM element top-right fixed.
  4. Patch server.py: přidat /cache/dashboard-data.json route proxying HAOS /local/smart-home/dashboard-data.json, timeout 2.5 s, 502/504 fallback. Existující /api/* Homey proxy intact, HOMEY_TOKEN count nezměněn.
  5. Sandbox tests T18 (loader fallback), T19 (cache proxy), T20 (visual contract). Run all 20 → 20/20 PASS.
  6. Backup do _backup_before_kiosk_phase_e_<timestamp>/ s SHA256 manifest + chromium command line snapshot.
  7. Atomic swap, restart server.py (pkill + nohup), F5 reload Chromium via xdotool přes DevTools port 9222.

Po reloadu badge skutečně tam je. Oranžový ◐ CACHE OLD top-right — protože HAOS cache vrátila JSON, ale s _stale: true (bridge_age_s > 300 s). Phase E chain se zachoval správně, dashboard nezůstal blank, všechny ovládací prvky preservovány.

Badge stavy:

  • ● CACHE — zelená, data čerstvá z HAOS cache
  • ◐ CACHE OLD — oranžová, cache OK ale data starší než 5 min
  • ○ HOMEY — modrá, fallback na Homey REST
  • ○ LOCAL — červená, fallback na localStorage last-good
  • ✕ ERR — všechny tři stages selhaly

R0 preflight — co teď zbývá

Phase R0 byla read-only diagnostika, kam dál v repair loopu. Sandbox PASS 20/20, snapshot 3 h, kiosek LIVE, cache 1.8 %. Ale HomeyScript memory watchdog log ukazuje 1906 cumulative restartů a oscilaci 110-149 MB každých 5-10 min. To je systémový tlak, který sandbox sám nevyřeší — vyžaduje patch na úrovni HomeyScript runtime.

R1-R3 plán:

  • R1 — bathroom vacancy hardening (user-visible problem, edge cases kdy zůstane svítit/hrát po opuštění)
  • R2 — HomeyScript memory pressure root cause + reduction
  • R3 — morning routine deterministic audit (alarm/no-alarm, before/after 03:00, late_motion_ignored pattern)

Compliance audit dnešního dne

RuleStatus
SANDBOX-FIRST (každá změna přes sandbox)
AUTO-REGISTRY (T17-T20 v ALL_TESTS)
SAFE_REPAIR_MODE (backup + SHA256 + rollback)
RPi/HAOS infrastructure role (Homey untouched)
No PC reads (kiosk Phase E rebuilt fresh)
0 Homey REST writes
0 HomeyScript edits
0 Advanced Flow edits
0 device renames
0 tokens v reportech

Takeaway

Pro tenhle systém už není „opravím to v Homey" první volbou. První je „postavím sandbox test, který to reprodukuje, a teprve když projde, připravím staging patch a počkám na explicit GO". To není byrokracie — to je rozdíl mezi domem, kde se občas v noci sám rozsvítí pásek u postele, a domem, který se dá testovat bez toho, aby si o tom obyvatel ráno četl v build logu.

Sandbox runner pokračuje hourly. Další běh na :00 zařadí T17-T20 do registry baseline. Live deploy gate zůstává OPEN — pending explicit per-change GO.