From 893a0de862cf9e646782c9a81ab783b371aae2d3 Mon Sep 17 00:00:00 2001 From: oskar Date: Thu, 16 Jul 2026 21:30:01 +0200 Subject: [PATCH] =?UTF-8?q?docs(sesja):=202026-07-16=20control-plane=20?= =?UTF-8?q?=E2=80=94=20dopisanie=204=20fixow=20(recon=20lustro,=20deploy-n?= =?UTF-8?q?ode=20--build,=20supervisor=20freeze,=20event=20flood)=20+=20ak?= =?UTF-8?q?tualizacja=20backlogu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sesja odkryla i naprawila wielowarstwowa awarie warstwy decyzyjnej: pusta kolejka akcji byla skutkiem zamrozonej petli supervisora (glob 358k eventow > timeout) i martwej retencji (cichy efekt uboczny fixu checkpointu z 07-15). Backlog: oznaczone ZROBIONE (deploy-node --build, supervisor frozen, event flood/retencja), dodane OTWARTE (ghost world_state, shadow_mode decyzja, drift->action, gokapi .env). Co-Authored-By: Claude Sonnet 5 --- docs/backlog.md | 92 +++++++++++++++++++++++++++++++++++-- docs/sessions/2026-07-16.md | 58 +++++++++++++++++++++++ 2 files changed, 145 insertions(+), 5 deletions(-) diff --git a/docs/backlog.md b/docs/backlog.md index 02de373..21bab92 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -74,6 +74,41 @@ Zweryfikować po zmianie, że `npm_api.py --npm vps` nadal łączy się przez wpis „ZNIKNĘŁY (potwierdzone reconem 2026-07-02)" w Zamkniętych zdezaktualizowany. **Fix**: ręczny `docker rm` hash-prefixed kontenerów control-plane na VPS; przy okazji sprawdzić, skąd wróciły (fix A `3b71707` miał blokować źródło divergence). +**Update 2026-07-16** (sesja `docs/sessions/2026-07-16.md`): po odetkaniu supervisora +(event flood + pętla zamrożona, oba naprawione dziś) potwierdzone, że wpisy `error` +w topologii panelu to dokładnie te ghosty — hash-prefixed w world_state observera, +NIE żywe kontenery (`docker ps -a exited=0` na VPS). Observer nie prune'uje wpisów +po zniknięciu kontenerów spod tych nazw. To trzyma `System Status: ERROR` fałszywie. +**Fix (do zrobienia)**: observer powinien weryfikować faktyczny stan kontenera przy +budowaniu world_state i prune'ować wpisy dla kontenerów, których już nie ma (ta sama +klasa błędu co „Rozjazd world-state observera: NOMINAL przed istnieniem" niżej). + +--- + +### shadow_mode → remediacja: decyzja o auto-restart + +**Data**: 2026-07-16 +**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane +**Problem**: po odetkaniu supervisora (event flood + pętla zamrożona, oba naprawione +dziś) kolejka akcji nadal pusta częściowo dlatego, że `shadow_mode=True` downgrade'uje +HA `container_restart` do `alert_only` — supervisor widzi problem, ale świadomie nie +enqueue'uje akcji restartu. +**Do decyzji**: czy i kiedy włączyć auto-restart padłych kontenerów — wymaga +architektury (guardraile, cooldowny, blast radius per serwis) zanim `shadow_mode=false`. +Patrz też istniejący wpis „ha-diag-agent deploy ZABLOKOWANY" niżej — przed +`shadow_mode=false` tam wymieniony konkretny target (`homeassistant5`). + +--- + +### gokapi: deploy-node VPS rzuca błąd — brakujący `.env` + +**Data**: 2026-07-16 +**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane +**Problem**: `deploy-node.sh` na VPS rzuca błąd na serwisie gokapi z powodu brakującego +`.env` (`/opt/homelab/config/gokapi/.env` nieutworzony lub niepełny — wzorzec z sesji +2026-07-09 `docs/sessions/2026-07-09-kb-configi-gokapi.md`). +**Fix**: sprawdzić `services/gokapi/env.example`, utworzyć/uzupełnić `.env` na VPS wg +konwencji `env.example` → `/opt/homelab/config//.env`. --- @@ -203,6 +238,13 @@ czy ma własną logikę de-duplifikacji blokującą enqueue. **Update 2026-07-02**: ghost kontenery (bug B) zniknęły z VPS — jeśli objaw wróci, hipoteza "źródłem są ghosty" jest już nieaktualna. UWAGA: ślepy supervisor na SATURN (brak mountu repo, patrz sesja 2026-07-02) to INNY przypadek — nie mylić z tym bugiem. +**Update 2026-07-16** (sesja `docs/sessions/2026-07-16.md`, druga połowa dnia): dwie +głębsze przyczyny pustej kolejki znalezione i naprawione (petla supervisora zamrożona +~24h — patrz „Supervisor: pętla zamrożona…" w Zamkniętych; event flood 358k plików +paraliżujący reconcile — patrz „Event flood…" w Zamkniętych). Po obu fixach supervisor +tika i reconcile się kończy, ALE objaw z tego wpisu (brak `redeploy` mimo widocznego +`error` — elasticsearch/diskover na piha, ollama solaria) **nadal aktualny** — drift→action +nie domyka się mimo odetkanego mózgu. Zostaje otwarte jako osobne dochodzenie. --- @@ -325,6 +367,44 @@ Długoterminowo: `agent.sh new` powinien odmawiać jeśli żądana gałąź jest ## Zamknięte +### Supervisor: pętla zamrożona ~24h, healthy ale nie tika — NAPRAWIONE (2026-07-16, commit `409b583`) + +**Data**: 2026-07-16 +**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane +**Było**: kontener supervisora `healthy`, ale pętla `reconcile()` nie tikała od ~24h, +zero logów. Root cause zweryfikowany na `/proc` (`State:S hrtimer_nanosleep`, NIE +deadlock): `glob` po `EVENTS_DIR` co cykl przy 358k plikach → cykl przekracza brak +timeoutu → nigdy się nie kończy. Logi na DEBUG maskowały objaw. +**Naprawione**: 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 (`SUPERVISOR_TICK_LOG_EVERY`) na INFO; healthcheck sprawdza +świeżość heartbeat, nie tylko czy proces żyje. Zweryfikowane w boju: pętla tika +(cycle #340→#480), cykl #1 timeoutował ale pętla kontynuowała = odporność działa. +**Lekcja**: „healthy kontener ≠ tikająca pętla" — healthcheck musi sprawdzać +świeżość ostatniego cyklu, nie samo czy proces odpowiada. + +--- + +### Event flood 358k plików + retencja martwa od fixu checkpointu — NAPRAWIONE (2026-07-16, commit `dff76ec`) + +**Data**: 2026-07-16 +**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane +**Było**: `EVENTS_DIR` = 358k plików, 91% to `service_healthy` emitowany co cykl per +serwis (stan-jako-zdarzenie, antywzorzec). Retencja `_cleanup_control_plane_fs()` +była martwa od fixu checkpointu `d5139c9` (2026-07-15, patrz „Bug: checkpoint +observera po ścieżce leksykalnej" niżej) — porównanie `str(ścieżka) <= checkpoint_int` +rzucało `TypeError` cicho łapany przez szeroki `except` → backlog rósł bez ograniczenia. +Naprawiając checkpoint wczoraj, złamaliśmy retencję, która na nim polegała. +**Naprawione**: (a) node-agent emituje `service_healthy` tylko na transition +unhealthy→healthy (funkcja dla `observer.process_event` zachowana); (b) retencja +naprawiona epoch-do-epoch; (c) `scripts/maintenance/cleanup_event_backlog.py` +(dry-run + `--apply`). 150 testów pass. Cleanup wykonany na PIHA+VPS: 272 232 pliki +usunięte, backlog 358k→12,7k. **Wynik**: reconcile supervisora przestał timeoutować. +**Lekcja**: migracja typu pola (ścieżka→epoch int) musi audytować WSZYSTKICH +konsumentów tego pola — szeroki `except` maskował dokładnie tę klasę regresji. + +--- + ### Ghost kontenery w panelu (Problem B) — ZNIKNĘŁY (potwierdzone reconem 2026-07-02) > ⚠️ ZDEZAKTUALIZOWANE 2026-07-06: ghosty znów widoczne na VPS — patrz wpis @@ -718,7 +798,7 @@ liveness). Dotyczy tez przyszlych: nextcloud, gokapi. **Zasada na przyszlosc:** rejestracja w services.yaml/topology to CZESC deployu, nie osobny krok "kiedys" — inaczej kazdy nowy serwis to slepy punkt monitoringu. -## Bug: deploy-node.sh nie przebudowuje obrazu — deploy "OK" ale nowy kod nie wchodzi (2026-07-15) +## Bug: deploy-node.sh nie przebudowuje obrazu — deploy "OK" ale nowy kod nie wchodzi (2026-07-15) — ✅ ZROBIONE (2026-07-16, commit `77defff`) **Objaw.** `deploy-node.sh ` robi `docker compose up -d` BEZ `--build`. Dla serwisów z Dockerfile (ha-diag-agent, node-agent, llm-gateway, brain-watchdog, itd.), @@ -736,10 +816,12 @@ force-recreate) i ha-diag-agent 2026-07-15 (fix node_name był w repo `f2ba81b`, compose. Docker cache'uje obraz po tagu, nie po zawartości src/. compose. Docker cache'uje obraz po tagu, nie po zawartości src/. -**Fix (do zrobienia).** deploy-node.sh powinien dla serwisów z Dockerfile wywoływać -`docker compose up -d --build` (przebuduje gdy src się zmienił; no-op gdy nie). Ewentualnie -`--force-recreate` gdy zmienił się env-file. Bez tego każdy code-only deploy wymaga -ręcznego rebuild — łatwo przeoczyć (deploy mówi green). +**Fix — ZROBIONE (2026-07-16, `77defff`).** deploy-node.sh wywołuje teraz `--build` +warunkowo, gdy serwis ma top-level `Dockerfile` (`test -f services//Dockerfile`); +prebuilt serwisy bez `--build` (no-op). Zweryfikowane w boju na PIHA: 6 serwisów +(node-agent/ha-diag/brain-watchdog/llm-gateway → Building; vikunja/kb-postgres → prebuilt). +**Follow-up pozostawiony**: `agent-system` ma build w podkatalogach bez top-level +Dockerfile — niezarejestrowany przez tę detekcję, osobny task. ## Ollama SOLARIA: brak sterownika NVIDII — ZAMKNIĘTE (2026-07-16) diff --git a/docs/sessions/2026-07-16.md b/docs/sessions/2026-07-16.md index daf073f..670a32b 100644 --- a/docs/sessions/2026-07-16.md +++ b/docs/sessions/2026-07-16.md @@ -13,3 +13,61 @@ - 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 1–8 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//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).