homelab-codex-ws/docs/sessions/2026-07-16.md

74 lines
7.7 KiB
Markdown
Raw Normal View History

---
## Wątek KB — krok 7 (pilot retrieval) + GPU: FAZA 2 MODUŁU 5 DOMKNIĘTA
**GPU przywrócone**: sekcja deploy w compose odkomentowana (merge `ollama-gpu-restore`), recreate, bge-m3 na 100% GPU. Benchmark: **207ms/embed vs 790ms CPU (~3.8× sekwencyjnie)** — overhead HTTP dominuje przy pojedynczych requestach, batching pozostaje dźwignią (backlog, przed fazą mailową). Zagadka znikającego kontenera po reboocie 15.07: jednorazowa, po boocie 16.07 wszystko wstało samo.
**Krok 7 — pilot retrieval (2683 chunki, zapytania przez bge-m3 + `<=>` cosine):**
- Kalibracja progów: **<0.45 trafienie, 0.450.55 szara strefa, >0.55 brak odpowiedzi** (negatywna kontrola "sernik" = 0.62, czysta separacja)
- Cross-lingual potwierdzony: angielskie zapytanie o FLL scoring lepsze niż polskie, wyciąga scoresheet mimo mojibake
- Najlepszy wynik: "innovation project scoring" → 0.387, top-5 spójnie z właściwego dokumentu
- **Chunking 600/150 zatwierdzony bez zmian**
- Findings do fazy 3 (nieblokujące): filtr chunków z binarnym OCR-szumem (przed kompilacją wiki!), deduplikacja dokumentów (paperless:14 ≡ 74), jakość OCR scoresheets
**Faza 2 modułu 5: kroki 18 komplet.** Baza: 225 216 kopert (gmail+paperless, cross-source), 2683 chunki z embeddingami, retrieval zweryfikowany. Następne: faza 3 — recon-plan (streszczenia+tagi jako pierwszy krok kompilacji, docelowo wiki-kompilat wg Karpathy'ego; Claude ma przygotować szkic sekcji wiki do recon-planu).
---
## Wątek control-plane — druga połowa dnia: "czemu supervisor nie generuje akcji naprawczych"
**Punkt wyjścia**: pytanie operatora o pustą kolejkę akcji odsłoniło wielowarstwową awarię warstwy decyzyjnej control-plane. Cztery kolejne fixy, każdy odkrywał następną warstwę:
### 1. RECON lustro shipping (Fable, commit `542bba4`)
`docs/infra/lustro-shipping-recon-2026-07-16.md` — 1507 mismatchy `lustro event=dead prom=up` w trwałym logu WYJAŚNIONE: (a) wczorajszy kontrolowany test (node-agent stał 3h20m, nie 15 min jak zakładano) + (b) poranny boot-race 56s. **Werdykt: shipping lustro działa, ZERO recurring problemu.** Prometheus 0 pomyłek w 48h — wzmacnia rekomendację GO dla Etapu 3 cutoveru.
Znaleziska poboczne: lustro biega na obrazie sprzed 5 tyg (deploy-node bez `--build` — patrz fix #2 niżej); fake-hwclock boot-race (RPi bez RTC — pierwszy event po boocie ma stary stempel, dropnięty przez timestamp checkpoint).
### 2. FIX `deploy-node.sh --build` (commit `77defff`)
`deploy-node.sh` robił `docker compose up -d` BEZ `--build` → dla serwisów z Dockerfile zmiany kodu NIE wchodziły (cicha rozbieżność repo↔runtime — backlog item z 2026-07-15, dziś naprawiony). Ugryzło 3×: fleet-prometheus, ha-diag, lustro.
Fix: warunkowy `--build` gdy serwis ma Dockerfile (`test -f services/<svc>/Dockerfile`), prebuilt bez `--build`. Zweryfikowany w boju na PIHA (6 serwisów: node-agent/ha-diag/brain-watchdog/llm-gateway → Building; vikunja/kb-postgres → prebuilt).
Edge case zgłoszony jako follow-up: agent-system ma build w podkatalogach bez top-level Dockerfile (niezarejestrowany w tej detekcji).
### 3. FIX supervisor resilience (commit `409b583`)
Supervisor był **funkcjonalnie zamrożony ~24h** — kontener `healthy`, ale pętla nie tikała, zero logów. Root cause zweryfikowany na `/proc` (proces `State:S hrtimer_nanosleep`, NIE deadlock): `reconcile()` robi `glob` po `EVENTS_DIR` co cykl, a to 358k plików → cykl przekracza timeout → nigdy się nie kończy. Plus logi na DEBUG = niewidoczne.
Fix: każdy cykl w `ThreadPoolExecutor` z `future.result(timeout=90s, env SUPERVISOR_RECONCILE_TIMEOUT)`; try/except owija cykl (wyjątek nie zabija pętli); tick-log co 10 cykli (env `SUPERVISOR_TICK_LOG_EVERY`) na INFO; healthcheck sprawdza świeżość heartbeat (nie samo że proces żyje). Zdeployowany. Po deployu: pętla tika (cycle #340→#480), cykl #1 timeoutował (ERROR "did not complete within 90s") ale pętla szła dalej = odporność działa.
### 4. FIX event flood + retencja + cleanup (commit `dff76ec`) — sedno problemu
Root cause braku akcji: `EVENTS_DIR` = 358k plików, 91% to `service_healthy` (szum "serwis zdrowy" emitowany co cykl per serwis).
**Krytyczne odkrycie**: mechanizm retencji `_cleanup_control_plane_fs()` był martwy od wczorajszego fixu checkpointu (`d5139c9`, 2026-07-15) — kod porównywał `str(ścieżka) <= checkpoint_int``TypeError` cicho łapany przez `except` → retencja przestała działać → backlog rósł bez ograniczenia. Naprawiając checkpoint wczoraj, złamaliśmy retencję, która na nim polegała.
Fix:
- (a) node-agent emituje `service_healthy` TYLKO na transition unhealthy→healthy (nie co cykl) — funkcja zachowana: `observer.process_event` nadal konsumuje `service_healthy` do `services.json[key].status=healthy` + rozwiązuje incydent, więc nie wycięte, tylko ograniczone do transition;
- (b) retencja naprawiona epoch-do-epoch (ta sama logika co `_checkpoint_ts_from_value`);
- (c) skrypt `scripts/maintenance/cleanup_event_backlog.py` (dry-run + `--apply`, `min_age` 3600s, kasuje tylko `service_healthy`/`node_health` starsze niż checkpoint, zachowuje `healthcheck_failed`/incydenty/wszystkie sygnały).
150 testów pass. Zdeployowany node-agent na PIHA+VPS. Cleanup wykonany: usunięto 272 232 pliki, backlog 358k→12,7k. Ghost dir `be17cb6eb0f6` usunięty. **Wynik**: reconcile supervisora przestał timeoutować (0 "did not complete" w 5 min), glob 12,7k, mózg odetkany.
### Stan końcowy
Control-plane supervisor odblokowany (tika, reconcile się kończy, retencja działa automatycznie co cykl). Ale kolejka akcji nadal pusta — bo (a) `shadow_mode=True` downgrade'uje HA `container_restart` do `alert_only`, (b) wpisy `error` w topologii to ghost hash-prefixed w world_state observera (NIE żywe kontenery — `docker ps -a exited=0` na VPS), observer nie czyści wpisów po zniknięciu kontenerów.
### Lekcje
- **Cichy efekt uboczny migracji typów.** Fix checkpointu `d5139c9` (ścieżka→epoch int) cicho zepsuł retencję `_cleanup_control_plane_fs()`, bo ta porównywała `str <= int` i łapała `TypeError` w szerokim `except`. Migracja typu pola musi audytować WSZYSTKICH konsumentów tego pola, nie tylko miejsce zmiany — szeroki `except` maskuje dokładnie tę klasę regresji.
- **"Healthy kontener ≠ tikająca pętla."** Docker healthcheck sprawdzający tylko "czy proces żyje" nie łapie pętli zawieszonej na blokującym wywołaniu bez timeoutu. Healthcheck musi sprawdzać świeżość heartbeat/ostatniego cyklu, nie tylko czy proces odpowiada.
- **`service_healthy` jako plik-event per cykl to antywzorzec.** Stan ("serwis jest zdrowy") emitowany jako zdarzenie na każdym cyklu miesza dwa różne pojęcia (stan vs zdarzenie) i skaluje się liniowo z (liczba serwisów × liczba cykli) — dokładnie to, co wygenerowało 358k plików. Emitować tylko na transition.
- **Równoległe sesje = ciągłe rozjazdy mastera.** Kilka CC działających jednocześnie na różnych worktree podnosi ryzyko push divergence — dyscyplina "zatrzymaj się i zgłoś" przy konflikcie jest tańsza niż force-push.
### Otwarte (następne sesje, osobne świadome tematy)
- Ghost-wpisy w world_state observera (hash-prefixed `error` w topologii mimo 0 exited kontenerów — observer nie prune'uje zniknionych kontenerów ze stanu). To trzyma `System Status: ERROR` fałszywie.
- `shadow_mode` → remediacja: decyzja czy włączyć auto-restart padłych kontenerów (architektura, guardraile, cooldowny).
- Czemu supervisor nie generuje akcji redeploy mimo widocznych realnych error (elasticsearch/diskover na piha, ollama solaria) — drift→action nie domyka się.
- gokapi `.env` not found (deploy-node VPS rzuca błąd na gokapi — brakujący `.env`).
- Etap 3 cutover per-node (dane gotowe, recon GO).