# Sesja 2026-07-06 — cutover liveności na Prometheus: recon starego toru + Etap 0 udowodniony w boju

## Cel

Główny projekt: Prometheus jako źródło prawdy liveności floty. Dwa kroki tej sesji:
(1) read-only recon starego toru liveności z dokładnymi punktami przełączenia
i planem etapowym; (2) Etap 0 cutoveru — bojowy dowód end-to-end toru
Prometheus → brain-watchdog → Telegram, którego brakowało od 2026-06-30.

---

## ZROBIONE

### Recon cutoveru — wmergowany (commit `d94bb38`)

Wynik: `docs/infra/prometheus-cutover-recon-2026-07-06.md` (517 linii, read-only,
zero zmian w kodzie). Kluczowe ustalenia:

- **Cutover to podmiana klasyfikacji liveności w JEDNYM miejscu** —
  `_prune_stale_world` w `scripts/observer/observer.py:312-323` + neutralizacja
  event-driven flipów `:438-446` — a NIE odłączanie rury. Fizycznie nic nie znika.
- **`node_health` wozi też metryki** disk/mem/cpu do panelu i `disk_pressure`
  do supervisora → rsync + eventy ZOSTAJĄ w całości.
- **Supervisor-remediacja i executor są CAŁKOWICIE niezależne od liveności węzłów**
  — supervisor czyta z `nodes.json` wyłącznie `disk_pressure`. Jedyny konsument
  liveności po stronie supervisora to syntetyczne eventy przejść
  (`node_offline/stale/online` → alert_only → Telegram).
- **Różnica semantyk**: `up{}` mierzy "host żyje (exporter odpowiada)",
  eventowy `node_health` mierzy "node-agent żyje + cały łańcuch SSH/rsync działa".
  Cutover ZWĘŻA definicję liveności — padnięty node-agent przy żywym węźle zmienia
  werdykt. Do świadomej akceptacji w etapie 2; rozważyć `up{job=node-agent}`.
- **chelsty-infra ma liveność WYŁĄCZNIE z eventów** (Prometheus go nie scrape'uje
  po LTE) → cutover MUSI być per-węzeł (`PROM_LIVENESS_NODES`), nigdy globalny.
- **Prometheus po cutoverze = niepilnowany SPOF** (`mem_limit 512m`,
  `oom_score_adj 200` — ubijalny przed control-plane) → fail-open na stary tor
  obowiązkowy + watchdog na sam Prometheus (mały task przy etapie 3).
- Exportery piha/solaria/lustro istnieją **poza GitOps** (brak definicji w repo)
  — rozjazd, osobny wątek.

### Etap 0 cutoveru — DONE, dowód bojowy end-to-end

Tor **Prometheus → brain-watchdog → Telegram udowodniony END-TO-END po raz
pierwszy w produkcji**. Test punktowy na VPS (celowo NIE w repo): tymczasowa
reguła `AlertTestEtap0` (`up{node="vps"}==1`, `for: 0s`) dodana do
`services/fleet-prometheus/rules/` na VPS + reload. Cztery punkty potwierdzenia:

1. **Firing w Prometheusie**: `/api/v1/alerts` → `AlertTestEtap0` firing, node vps.
2. **Watchdog spollował z PIHA**: log `[prometheus] sent alert: AlertTestEtap0:vps`
   — przy okazji widoczne dwa niezależne tory (ping mózgu OK co 60 s + poll
   Prometheusa osobno), zgodnie z architekturą A.
3. **Alert doleciał na Telegram** (@okitaialerts_bot): „Prometheus alert:
   AlertTestEtap0, Node: vps, Summary: …".
4. **Rollback czysty**: reguła usunięta, reload, firing=0.

Zamyka pending z 2026-06-30 („czy watchdog realnie polluje" — recon 2026-07-02
potwierdził konfigurację, dziś potwierdzone działanie end-to-end). Reguła testowa
NIE trafiła do repo — to diagnostyka, nie architektura; katalog `rules/` ma
nadal tylko `liveness.yml`.

### Realny dowód „czemu cutover" — chelsty NOMINAL-zonk

Panel agents.okit.pl pokazuje **chelsty NOMINAL + Runtime online, mimo że chelsty
jest OFFLINE od 34 dni** (`tailscale status`). U wszystkich węzłów: Last Seen
„Invalid Date", Connectivity/Incidents `undefined`, a status NOMINAL — observer
NIE egzekwuje świeżości `last_seen` (bug TTL wraca, patrz backlog „Observer
staleness"). Cutover na Prometheus `up{}` to naprawia: chelsty nie-scrape'owany →
down, nie fałszywie NOMINAL.

Snapshot panelu potwierdził też dwa zaległe drobiazgi (dopisane do backlogu):

- **Ghosty B wróciły/nieposprzątane** — hash-prefixed kontenery control-plane
  widoczne na VPS (wpis „zniknęły" z 2026-07-02 zdezaktualizowany).
- **elasticsearch + diskover w stanie error na PIHA** — usunięte w module 0
  (2026-07-02), ale observer wciąż je zna i raportuje.

---

## PLAN — następne etapy cutoveru (osobne sesje)

- **Etap 1 — shadow-read**: observer czyta OBA źródła (eventy I Prometheus
  `up{}`), pisze nienaruszające pola `prom_*` w `nodes.json`, loguje rozbieżności,
  NIC nie przełącza (parallel-run, fail-open). Realna zmiana w
  `observer.py:312-323` → worktree/CC.
- **Etap 2 — analiza**: ≥ 7 dni zgodności → klasyfikacja rozbieżności
  (semantyka vs bug) → decyzja mappingu (rekomendacja: `timestamp(up)` jako
  prom-last_seen przez istniejące `compute_liveness`).
- **Etap 3 — przełączenie per-węzeł** za flagą `PROM_LIVENESS_NODES`:
  najpierw solaria+lustro (najmniejsza szkoda przy pomyłce), potem vps+piha.
  **chelsty-infra ZOSTAJE na eventach.** Rollback = wyczyszczenie flagi.

---

## Wnioski

- **Etap 0 zamknął fundamentalną niepewność wiszącą od 2026-06-30**: „czy tor
  alertowy Prometheus→watchdog→Telegram w ogóle działa" przestało być wnioskowaniem
  z konfiguracji (recon 2026-07-02), a stało się faktem dowiedzionym w produkcji.
  Dalsze etapy cutoveru budują na sprawdzonym fundamencie, nie na założeniu.
- **verify-before-fix w reconie obalił założenie architektoniczne**: observer NIE
  jest jednym modułem w `services/control-plane/src/` — to `scripts/observer/observer.py`
  + wydzielona maszyna stanów `liveness.py` importowana przez trzy konsumentów.
  Bez reconu plan cutoveru celowałby w złe miejsce.
- **Test punktowy (reguła for:0s + rollback) to tani sposób na dowód bojowy toru
  alertowego** — bez czekania na realną awarię i bez commitowania diagnostyki do repo.
- Hashe sesji: `d94bb38` (recon cutoveru); Etap 0 = czynność operatorska na
  runtime (bez commitu, zgodnie z planem z reconu).
