--- okf: "0.1" type: session-log visibility: private status: active updated: 2026-07-15 links: [] --- # Sesja 2026-07-15 — Prometheus cutover Etap 2 (analiza GO) + domknięcie rodziny bugów event-pipeline/checkpoint ## Kontekst Kontynuacja projektu cutoveru liveności floty na Prometheus (start 2026-06-22, Etap 0 2026-07-06, Etap 1 shadow-read wdrożony). Równolegle: domknięcie rodziny bugów event-pipeline/checkpoint zapoczątkowanej fixem `d5139c9` (2026-07-14). ## ZROBIONE ### 1. fix(observer) `d5139c9` — checkpoint po timestampie, nie ścieżce leksykalnej ROOT CAUSE cichej ~34-dniowej "śmierci" PIHA dla observera: plik `evt-unknown--...` leksykalnie większy niż `evt-piha--...` ("p" < "u") zatruł checkpoint węzła — każdy nowy event PIHA uznawany za starszy i pomijany na zawsze. Fix: checkpoint = ostatnio przetworzony TIMESTAMP (int epoch), parsowany z nazwy `evt---...` (fallback mtime, **nigdy 0** dla istniejącego pliku — 0 = leksykalne "starszy niż checkpoint" = dokładnie ten poison), migracja starych path-checkpointów przy starcie. Zdeployowany na VPS (observer `StartedAt` 07-14). Zweryfikowany dziś jako kompletny i zdeployowany. Szczegóły: `docs/backlog.md` (sekcja "Bug: checkpoint observera po ścieżce leksykalnej"). ### 2. docs(infra) analiza Etapu 2 shadow-run (Fable, `8fec62d`) `docs/infra/prometheus-shadow-etap2-analiza-2026-07-15.md` — 165 mismatchy `SHADOW_LIVENESS_MISMATCH` solaria/lustro w dobie 2026-07-14 WYJAŚNIONE: dwa nocne wyłączenia węzłów (lustro 21:30 UTC — regularny power-off, solaria 21:34 UTC). Wzorzec `event=fresh prom=down` to **nie** "żywy węzeł niewidziany przez Prometheusa", tylko **martwy węzeł wykryty przez Prometheus w ≤45 s**, podczas gdy tor eventowy potrzebował pełnych 600 s TTL (`last_seen_age` rósł monotonicznie 29→597 s; mismatche ustają dokładnie na granicy TTL-dead). **Werdykt**: żaden z 165 mismatchy nie jest błędem Prometheusa — Prometheus był w każdym przypadku szybszy i miał rację. **Rekomendacja Etap 3: GO**: - vps, piha (always-on) — bez zastrzeżeń, 84 h ciągłego `up==1`, 0 mismatchy. - solaria, lustro (intermittent) — `prom=down` przy wyłączeniu jest prawdziwy, NIE dyskwalifikuje z cutoveru; planowe okna off → wątek anomaly detection (osobny, niezależny, nieblokujący). - Mapping: `prom-last_seen = timestamp(up)` wpuszczone w istniejące `compute_liveness` (potwierdzenie rekomendacji z recon). Ograniczenie dowodowe: twarde logi docker pokrywają tylko ~20 h (recreate kontenera 07-14 16:30 skasował wcześniejsze) — wnioski podparte pośrednio 84 h historii `up{}` + eventami przejść od 07-11; formalne "GO" czeka na ~7 dni czystych danych z trwałego logu (pkt 3 niżej), cel ~2026-07-20. ### 3. feat(observer) `9e7ed3e` — trwały log SHADOW_LIVENESS_MISMATCH Zapis do `/opt/homelab/logs/observer/shadow-liveness.log` (`RotatingFileHandler` 5MB×5, osobny logger `observer.shadow`, `propagate=False`, mismatch idzie i do stdout, i do pliku) — przeżywa docker recreate (recreate 07-14 zjadł materiał dowodowy analizy z pkt 2, stąd konieczność tego fixu). Świadomie **bez** nowego mountu (footgun: Docker tworzy brakujący bind-source jako root, uid 1000 nie miałby prawa zapisu) — wykorzystany istniejący mount `/opt/homelab`. Fail-safe: błąd zapisu do pliku nie wywala observera. Zdeployowany, potwierdzony (test lustro niżej wylądował w pliku, 162 KB). ### 4. Test kontrolowany event=dead / prom=up (lustro/pimirror2, 100.99.85.73) Zatrzymano node-agenta na żywym węźle (host up, eventy przestają płynąć) → observer po TTL uznaje węzeł za dead → shadow zalogował `node=lustro event=dead prom=up` DO TRWAŁEGO PLIKU. Waliduje drugi kierunek rozbieżności (nie wystąpił naturalnie w oknie analizy z pkt 2) razem z działaniem trwałego logu na realnym zdarzeniu. Node-agent przywrócony po teście. Dostęp do lustro: tylko `pi@` (hasło) — klucz `oskar` z SOLARII nieautoryzowany na tym hoście; hostname faktyczny `pimirror2`. ### 5. fix(ha-diag) `f2ba81b` — node_name z env + fail-fast na "unknown" ŹRÓDŁO trucizny checkpointu z pkt 1: `config.py` `Field(default="unknown", validate_default=True)` + validator odrzucający `""`/`"unknown"`; `main.py` → `SystemExit(1)` FATAL przy braku `NODE_NAME`; `EventEmitter.__init__` jako ostatnia bramka przed nazwą pliku eventu. +18 testów, 0 regresji. Zmergowany i **zdeployowany na PIHA** (agent `Up healthy` po rebuild — `NODE_NAME=piha` dochodzi do procesu). Pliki `evt-unknown-*` na VPS/PIHA: 0 (potwierdzone). ### 6. docs(backlog) `c858dbc` — bug deploy-node.sh brak `--build` Deploy raportuje "OK", ale dla serwisów z Dockerfile bez zmiany compose/env kontener zostaje na starym obrazie (Docker cache'uje po tagu, nie po zawartości `src/`) — cicha rozbieżność repo↔runtime. Ugryzło dwa razy: fleet-prometheus (config nie wchodził bez `--force-recreate`) i ha-diag-agent dziś (fix z pkt 5 był w repo, `Running` zamiast rebuild — wymagał ręcznego `docker compose up -d --build --force-recreate`). Fix pozostaje TODO w backlogu. ## STAN CUTOVERU - Etap 0 (dowód bojowy Prometheus→watchdog→Telegram): ✅ zamknięty (07-06). - Etap 1 (shadow-read, observer czyta oba źródła, nic nie przełącza): ✅ wdrożony. - Etap 2 (parallel-run + analiza zgodności): **TRWA**. Dane po fixach z pkt 1 i 3 czyste; analiza z pkt 2 gotowa z rekomendacją GO, ale formalnie czeka na dłuższe okno trwałego logu (cel ~2026-07-20). - Etap 3 (przełączenie per-węzeł, `PROM_LIVENESS_NODES`) — czeka na domknięcie Etapu 2. ## NASTĘPNE (po ~2026-07-20) - Etap 3 per-node: najpierw `solaria,lustro`, potem `vps,piha` (kolejność z recon, minimalizacja blast-radius). - Watchdog na sam Prometheus (SPOF po cutoverze — dziś nikt nie alarmuje o jego śmierci; `mem_limit 512m` + `oom_score_adj 200` czynią go ubijalnym przed control-plane). - `deploy-node.sh --build` (pkt 6 / backlog `c858dbc`). - Observer powinien odrzucać/kwarantannować event, którego `node` w treści ≠ katalog docelowy (druga warstwa obrony po fixie z pkt 5). ## Wnioski - **Rodzina bugów uid/gid + checkpoint + node_name to jeden motyw: "ścieżka/nazwa pliku ≠ tożsamość".** Checkpoint leksykalny po ścieżce (pkt 1), plik `evt-unknown-*` lądujący w cudzym katalogu (pkt 5), stary tech-debt uid/gid per-host (backlog 2026-07-10) — wszystkie trzy to ten sam wzorzec: system ufa POŁOŻENIU/NAZWIE zamiast jawnie zweryfikowanej tożsamości, i cichnie zamiast fail-fastować, gdy te się rozjadą. Fix z pkt 5 domyka jeden konkretny wektor (`NODE_NAME` nieustawione → fail-fast), ale ogólny wzorzec (observer nie waliduje `node` w evencie względem katalogu) zostaje jako TODO. - **`deploy-node.sh` bez `--build` to osobny, ortogonalny motyw "cicha rozbieżność deploy↔runtime"** — nie dotyczy tylko configów (backlog 2026-06-26), ale też kodu; ugryzło dwa razy w jednym dniu dzisiejszej sesji. - **Trwały log (pkt 3) był koniecznym fixem, nie kosmetyką** — recreate kontenera observera 07-14 skasował materiał dowodowy w trakcie samej analizy Etapu 2; bez trwałego logu każdy kolejny recreate zerowałby okno danych i cofał kryterium "≥7 dni czystych danych" do zera. - **Równoległe sesje CC dopisują commity do mastera w trakcie pracy** — commity `f57a01a`/`38cb204` (ollama/SOLARIA) wylądowały między `f2ba81b` i `9e7ed3e` w historii, mimo że nie są częścią tego wątku. Nie spowodowało konfliktu tym razem (worktree bazował na commicie sprzed rozjazdu, fast- forward czysty), ale potwierdza zasadę z CLAUDE.md: przed push zawsze ff-check + rebase, nie zakładać, że `master` stoi w miejscu. - **Terminal gubił wklejki blokami** podczas sesji (drobna operacyjna uciążliwość, bez wpływu na wynik) — odnotowane jako obserwacja, nie bug do śledzenia. Hashe sesji: `d5139c9`, `8fec62d` (analiza Fable), `9e7ed3e`, `f2ba81b`, `c858dbc`. --- ## Wątek KB — moduł 5 faza 2: kroki 3-6 domknięte (sesja 14-15.07) **Krok 3 — token API Paperless:** konto dedykowane `kb-ingest` (superuser), token w `/opt/homelab/kb/.env` na PIHA (600). Token rotowany w trakcie (wyciek do transkryptu wykryty przez samego CC; stary unieważniony, nowy zweryfikowany bez wypisania). Base URL: `http://192.168.31.5:8210`. **Krok 4 — gmail-header-backfill:** finalnie **225 030/225 030** kopert z headers entities. Po drodze: pełny run zostawił 5008 braków → diagnoza: 9 realnych parse-errorów + **4999 zgubionych cicho** przez `json.dumps` poza per-wierszowym try (TypeError na 8-bit `Date:`, zabity cały slice offset 70000). Fix: compat32 fallback, `missing_file` counter, bilans statystyk jako inwariant (`scanned = suma wyników`, niezerowy exit przy rozjeździe). Re-run braków odzyskał wszystko. Bonus: audyt `gmail-bulk-import` (empiryczne repro) znalazł tę samą klasę bomb → hardening wdrożony (`_sanitize` przeniesiony do `packages/kb-mail`, per-wiadomościowy try, odporny `_flush`, testy regresyjne). Lekcja systemowa: długie joby na nodach zawsze z logiem do pliku (`> run.log 2>&1`), nie goły tmux — utrata logów tmuxa kosztowała godzinę diagnozy. **Krok 5 — adapter Paperless→koperta:** 186 kopert `source='paperless'` (idempotencja: re-run 0 inserted / 186 already_in_db), **cross-source join działa: 180/186 z `source_mail`** + przykład end-to-end (paperless:119 ↔ koperta mailowa via scoresheets.pdf) — krok 8 planu de facto zaliczony. 6 dokumentów bez joinu do obejrzenia przy kroku 7 (świadomość, nie fix). **Krok 6 — chunk+embed:** **2683/2684 chunków** w `document_chunk` (160 dokumentów; 1 patologiczny dot-leader chunk odrzucony przez context window Ollamy — known limitation). Timing CPU: **0.79s/chunk, ~35 min pilot** — twardy wniosek: pełny mail-corpus wymaga GPU i/lub batchowanych wywołań Ollamy. Multi-agent code review (5 kątów) złapał 3 realne bugi przed produkcyjnym runem (infinite-loop przy overlap≥size, izolacja błędów insert_chunk, ciche liczenie ON CONFLICT jako insertów). Follow-up ważny: `UNIQUE(envelope_id, chunk_index)` bez `model` — drugi model embeddingów cicho by się no-opował (schema change przy fazie 3). **Infra — Ollama na SOLARII deklaratywnie:** brakujący wpis w `hosts/solaria/services.yaml` był root-cause "declared but not running". Cutover: modele (36G) przeniesione do `/opt/homelab/data/ollama`, systemd disabled, bind 127.0.0.1 + Tailscale IP. Odkrycia: brak nvidia-container-toolkit (doinstalowany, prerequisite do runbooka) oraz **brak sterownika NVIDII na hoście w ogóle** — "GPU-backed" w manifestach było fikcją, wszystko zawsze szło na CPU. Sterownik = backlog, blokuje fazę mailową embeddingów. Reachability z PIHA (llm-gateway) potwierdzona. **Decyzja architektoniczna fazy 3:** moduł syntezy jako wiki-kompilat wg wzorca Karpathy'ego llm-wiki (gist 442a6bf...): RAG/pgvector zostaje warstwą dowodową, nad nim LLM-utrzymywana wiki markdownów (strony-encje, cross-linki, lint), każda strona linkuje envelope_id/chunki źródłowe; utrzymanie przez CC/API (lokalny 30B CPU-only za słaby). Szczegóły w pamięci Claude + do recon-planu fazy 3. **Następne wejście:** krok 7 — pilot retrieval (sesja ręczna, SQL `ORDER BY embedding <=> query`, ocena jakości, kalibracja chunking 600/150). Ostatnia bramka fazy 2.