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:
- SSH na kiosek, pull
/home/smart/dashboard/index.html+server.pyna HAOS staging dir. - 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ů.
- Patch HTML: nahradit 7-řádkové
fetchDashboardData()za 60-řádkovou verzi se 3-stage chainem cache→Homey REST→localStorage. Přidat#sourceBadgeCSS + DOM element top-right fixed. - Patch server.py: přidat
/cache/dashboard-data.jsonroute proxying HAOS/local/smart-home/dashboard-data.json, timeout 2.5 s, 502/504 fallback. Existující/api/*Homey proxy intact,HOMEY_TOKENcount nezměněn. - Sandbox tests T18 (loader fallback), T19 (cache proxy), T20 (visual contract). Run all 20 → 20/20 PASS.
- Backup do
_backup_before_kiosk_phase_e_<timestamp>/s SHA256 manifest + chromium command line snapshot. - 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
| Rule | Status |
|---|---|
| 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.