Sesja: analiza Etapu 2 (Fable) rekomenduje GO dla wszystkich 4 węzłów; 165
mismatchy solaria/lustro wyjaśnione jako detection-lag TTL vs scrape, nie bug.
Trwały log SHADOW_LIVENESS_MISMATCH zweryfikowany testem kontrolowanym na
lustro. Backlog: ha-diag-agent node=unknown oznaczone ZROBIONE (f2ba81b).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.8 KiB
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=downprzy 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ącecompute_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, potemvps,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 200czynią go ubijalnym przed control-plane). deploy-node.sh --build(pkt 6 / backlogc858dbc).- Observer powinien odrzucać/kwarantannować event, którego
nodew 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_NAMEnieustawione → fail-fast), ale ogólny wzorzec (observer nie walidujenodew evencie względem katalogu) zostaje jako TODO. deploy-node.shbez--buildto 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ędzyf2ba81bi9e7ed3ew 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ć, żemasterstoi 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.