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

160 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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-<ts>-...` leksykalnie większy niż `evt-piha-<ts>-...` ("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-<node>-<ts>-...` (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.