homelab-codex-ws/docs/sessions/2026-07-15.md
oskar 6dffa5c565 fix(kb): przepiecie wszystkich odwolan wewnetrznych po migracji
126 plikow (md, yaml, sh, py) odwolywalo sie do sciezek sprzed migracji.

  15  markdown-linkow [..](..) -> policzona sciezka WZGLEDNA wobec pliku
      odsylajacego (wczesniej czesc z nich byla repo-root-relative i nie
      rozwiazywala sie z katalogu, w ktorym lezala)
 200  odwolan tekstowych (backticki, proza, yaml, importy w kodzie)
      -> nowa sciezka repo-root-relative, zgodnie z konwencja repo
   5  linkow rodzenstwa (gole nazwy plikow, np. "](DEPLOY.md)") — dzialaly
      tylko w starym katalogu; przeliczone recznie

Objete m.in.: CLAUDE.md (scripts/onboard/README.md -> kb/runbooks/
node-onboarding-tool.md, docs/backlog.md -> kb/phases/backlog.md),
README.md, .claude/skills/, 20 session logow, kod jobow.

Ostatnie 5 odwolan pochodzi z tresci wciagnietej rebasem z origin/master
(session log 2026-07-31, override node-agenta na SOLARII, dwie pozycje
backlogu) — wskazywaly na docs/incidents/, docs/kb/modules/ i
services/narty27/README.md sprzed migracji.

Dodany wzajemny link miedzy kb/services/control-plane.md (stub kodu)
a kb/subsystems/control-plane.md (opis, deprecated) — dwa dokumenty o tym
samym systemie, latwe do pomylenia.

Weryfikacja na 790 plikach: 0 odwolan do starych sciezek,
0 martwych linkow markdown. Lint OKF: 190/190 plikow ZGODNE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:21:16 +02:00

11 KiB
Raw Blame History

okf type visibility status updated links
0.1 session-log private active 2026-07-15

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: kb/phases/backlog.md (sekcja "Bug: checkpoint observera po ścieżce leksykalnej").

2. docs(infra) analiza Etapu 2 shadow-run (Fable, 8fec62d)

kb/phases/prometheus-cutover-etap2.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.pySystemExit(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.