---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-08-03
links:
  - ../phases/backlog.md
---
## Aktywne

### Bug upstreamu `GoogleCloudPlatform/knowledge-catalog`: generator grafu pomija linki od `/`

**Data**: 2026-07-31
**Źródło**: pilot fazy 5 — narty27 (`docs/sessions/2026-07-31-kb-f4-final-narty27.md`,
sekcja „Wnioski do przeniesienia na fazę 5", pkt 3)
**Problem**: generator grafu z `knowledge-catalog` pomija linki zaczynające się od `/`
(ścieżki absolutne), wbrew §5.1 **własnej spec** OKF, która je dopuszcza. Efekt: część
krawędzi grafu po prostu nie powstaje — cicho, bez ostrzeżenia. Rozbieżność
implementacja↔spec po stronie upstreamu, nie naszej konfiguracji.
**Obejście (zastosowane)**: własny generator grafu (cytoscape) w pilocie narty27 —
nie używamy generatora upstreamu.
**Fix**: zgłosić issue/PR do `GoogleCloudPlatform/knowledge-catalog` (minimalny repro:
dokument z linkiem `/foo` → brak krawędzi w wyjściu grafu, mimo §5.1). Jeśli upstream
naprawi — rozważyć powrót z własnego generatora przy fazie 5 (wiki-kompilat), żeby nie
utrzymywać własnego kodu bez potrzeby.

---

### `hosts/solaria/runtime/ollama/docker-compose.override.yml` — brak w repo (rozjazd repo↔runtime)

**Data**: 2026-07-31
**Źródło**: sesja 2026-07-31 (`docs/sessions/2026-07-31-kb-f4-final-narty27.md`,
sekcja „Otwarte po sesji" pkt 2)
**Problem**: `ollama` jest zadeklarowana w `hosts/solaria/services.yaml` (rola
`llm-inference`, exposure `private` = bind na `TAILSCALE_BIND_IP` + loopback), ale
`hosts/solaria/runtime/` zawiera wyłącznie `node-agent` i `stability-agent` — override
dla `ollama` **nie istnieje w repo**. Host-specific konfiguracja żywego kontenera
(rezerwacja GPU, bind, env) nie jest zatem wersjonowana: repo nie opisuje tego, co
faktycznie biega na SOLARII. Ta sama klasa rozjazdu repo↔runtime co przy węzłach
repo-less — dowolny redeploy z repo może wystawić serwis inaczej, niż działa dziś.
Wzorzec docelowy istnieje obok: `hosts/piha/runtime/ollama-piha/docker-compose.override.yml`.
**Fix**: zrekonstruować override z żywego kontenera na SOLARII (`docker inspect` →
bind/porty/GPU/env/limity) i zacommitować jako
`hosts/solaria/runtime/ollama/docker-compose.override.yml`; zweryfikować, że deploy
z repo daje kontener identyczny z obecnym **zanim** ktokolwiek zrobi redeploy.

---

### `services/narty27`: `exposure: private` w `service.yaml` + README vs faktyczna publiczna ekspozycja (NPM VPS + LE)

**Data**: 2026-07-31
**Źródło**: pilot fazy 5 — narty27 (`docs/sessions/2026-07-31-kb-f4-final-narty27.md`);
znalezione przy porządkowaniu backlogu po tej sesji
**Problem**: kontrakt serwisu deklaruje `private` — `services/narty27/service.yaml:5`
(`exposure: private # LAN/Tailscale only; no npm vhost, no public ingress`) i to samo
w `kb/runbooks/narty27-deploy.md` — podczas gdy `narty27.kapala.org` jest **publiczne**
(NPM na VPS + cert Let's Encrypt). Pole `exposure` steruje traktowaniem ekspozycji
przez agentów (patrz „Discovery Entry Points for Agents" w CLAUDE.md — `service.yaml`
jest kontraktem operacyjnym, z którego agent czyta, jak zarządzać serwisem), więc
rozjazd kontrakt↔rzeczywistość jest tu groźniejszy niż zwykła nieaktualna
dokumentacja: agent podejmie decyzję na podstawie pola, które kłamie.
**Fix (osobny task)**: (1) **najpierw** zweryfikować, co realnie konsumuje pole
`exposure` (observer / supervisor / ścieżka deployu) — dopiero to pokaże, czy poza
dokumentacją zmiana coś przestawia; (2) potem poprawić `service.yaml` + README na
`public`, z komentarzem wskazującym NPM VPS host **#14** i cert LE **#38**.

---

### `scripts/ha/deploy.sh --delete`: brak wspólnego toru kasacji automatyzacji

**Data**: 2026-07-27
**Źródło**: sesja porządków po audycie (`task/ha-porzadki`); kontekst bezpośredni:
kasacja pkt 10 (`36b43e5`, para Tymka/powitanie-test/notify-router/para-prototyp)
zrobiona pętlą ręcznych `curl DELETE` po API zamiast przez repo tooling.
**Problem**: `deploy.sh` ma tylko WRITE (`POST /api/config/<domain>/config/<id>`) —
zgodnie z DESIGN.md ("Deploy path") i istniejącym wpisem w tym backlogu (sekcja
"Cutover HA ken", krok 4) DELETE obiektów usuniętych z repo jest poza zakresem,
drift-check tylko ostrzega (`warnings`), nigdy nie kasuje. Efekt: jedyna droga
usunięcia automatyzacji z żywej instancji to ręczne wywołanie API, bez
drift-check/`check_config`/verify, czyli bez żadnej z gwarancji, które deploy.sh
daje dla write.
**Fix**: `deploy.sh <instance> --delete <plik...>` (albo `--delete` jako tryb pracy
na plikach usuniętych z repo, wykrytych przez `drift_warnings`) z tym samym rytmem
co write: dry-run domyślny, `--dry-run`/LIVE jak dziś, `DELETE
/api/config/<domain>/config/<id>` per obiekt, verify (GET → oczekiwane 404) zamiast
porównania treści. Scope jak przy write: automations/scripts/scenes.

---

### HA ken: `input_number.klima_salon_tolerancja` nie ma triggera — zmiana nie przelicza progu

**Data**: 2026-07-27
**Źródło**: sesja porządków po audycie (`task/ha-porzadki`), przy okazji przeglądu
`1784804667795` dla konwencji automatyzacji (pkt 17)
**Problem**: `"Klima salon: włącz chłodzenie i synchronizuj cel"` (`1784804667795`)
ma trigger `id: sync` na `input_number.klima_salon_temp_docelowa` (zmiana celu od
razu przelicza próg), ale brak odpowiednika dla `input_number.klima_salon_tolerancja`
— to ta sama klasa buga co "trigger brzegowy bez lustrzanego warunku" z audytu
(sekcja 3, wzorzec `klima_salon` z fixu `faa2e2a`), tylko po stronie triggerów, nie
warunków: zmiana tolerancji na dashboardzie nic nie przelicza do najbliższej
naturalnej zmiany `sensor.thsalon_temperature`.
**Fix**: dodać trigger `state` na `input_number.klima_salon_tolerancja` do gałęzi
`sync` (albo do analogicznej gałęzi w automatyzacji OFF, jeśli tolerancja wpływa też
na próg wyłączenia) w `1784804667795`, tak samo jak istniejący trigger na
`_temp_docelowa`.

---

### HA ken: guard TRV kalibracji przed sezonem grzewczym (~2026-09)

**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcja 1.4
(pkt 2 checklisty operatora)
**Problem**: kalibracje TRV sypialnia (`1764751049013`) i Tymek (`1765817937658`) liczą
`room_temp` jako średnią z czujnika zhimi, który jest `unavailable` od 2026-07-17 →
`float(0)` w formule zaniża temperaturę o połowę → kalibracja dojechała do −5.0
(potwierdzone w fixtures). Latem (TRV `off`) nieszkodliwe; w sezonie grzewczym =
trwałe przegrzewanie obu pokoi. Dodatkowo wszystkie 6 automatyzacji TRV pollują co
40–50s (baterie 20–38%), a SalonPrawy ma clamp `diff` ±5 zamiast ±1.5 jak reszta.
**Fix (przed sezonem)**: (1) guard `is not unavailable` na czujnikach zhimi zamiast
ślepego uśredniania; (2) ręczny reset kalibracji sypialnia/Tymek po naprawie; (3)
zwolnić pętle do ≥5 min lub trigger na zmianę stanu; (4) ujednolicić clamp SalonPrawy;
(5) reanimować albo wyłączyć TRV łazienki (`number.*_calibration` unavailable —
urządzenie zniknęło z sieci).

---

### HA ken: przycisk graceful shutdown klimy salonowej na kartę dashboardu

**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcja 3.1,
fix-pack 1 (`task/ha-fix-pack-1`, DESIGN.md „Decyzje operatora po audycie 2026-07-23")
**Kontekst**: po fix-packu 1 „Klima salon: wyłącz…" (`1784804668795`) respektuje
`klima_salon_auto = on` dla gałęzi sunset/balkon; suszenie parownika przy ręcznym
zgaszeniu `klima_salon_auto` działa już tylko jako efekt uboczny przełącznika trybu
auto (osobna gałąź `choose` na trigger `auto_off`). Brakuje wygodnego jednego
przycisku „wyłącz klimę i osusz teraz" niezależnego od przełącznika auto.
**Fix**: dodać na dashboard przycisk/skrypt wywołujący `script.klima_salon_dry_off`
bezpośrednio (bez przełączania `klima_salon_auto`), żeby ręczne graceful shutdown nie
wymagało znajomości wewnętrznej logiki automatyzacji.

---

### HA ken: diagnoza wspólnej awarii sprzętowej 2026-07-17 (czujniki ruchu, pilot 4button, xiaomi_miot)

**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcje 1.2,
1.6, 4.5 (pkt 1, 3, 15 checklisty operatora)
**Problem**: restart HA / update Supervisora 2026-07-17 15:07 zbiega się z
`unavailable` na: klaster czujników ruchu/obecności (mdwejscie, mdsypialnia, mdheli —
przynajmniej od restartu; occusalon/mdtymka/mdubikacja gasną w kolejnych dniach —
obraz siadających baterii), całej integracji xiaomi_miot (fan.zhimi_mb3/mc2, czujniki
temperatury zhimi używane w kalibracjach TRV) i `sensor.4button_battery` (mimo że pilot
4button strzelał jeszcze 2026-07-12 — 8 automatyzacji salonu na tym urządzeniu). Nie
jest jasne, czy to jedna awaria (Zigbee coordinator/integracja) czy zbieg kilku.
**Fix**: (1) sprawdzić fizycznie baterie/re-pairing 6 czujników ruchu + czujnik
wycieku WC (pkt 1 checklisty); (2) zdiagnozować integrację xiaomi_miot po stronie HA
zamiast łatać pojedyncze automatyzacje (pkt 3); (3) sprawdzić fizycznie baterię pilota
4button (pkt 15). Reanimacja czujników ruchu odblokuje też ~15 cicho martwych
automatyzacji (alerty on-leave, nocne gaszenie, `Poranek start`) — patrz audyt sekcja
1.2 dla pełnej listy skutków.

---

### HA ken: projekt „architektura night_mode" (konsolidacja sleep/night mode)

**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcje 2.2,
2.4, 6 (pkt 7, 9 checklisty operatora — świadomie NIE załatane w fix-packu 1, patrz
DESIGN.md „Decyzje operatora po audycie 2026-07-23")
**Problem**: cztery flagi trybu (`sleep_mode`, `night_mode`, `passive_mode`,
`movie_mode`) o częściowo pokrywającej się semantyce, sprawdzane niespójnie (raz jedna
flaga, raz druga, raz OR obu); `automation.turn_off_sleep_mode_at_sunrise` steruje w
rzeczywistości `night_mode`. Cztery nakładające się automatyzacje gaszą ten sam zestaw
świateł nocą (`1702844937214` martwa, `1763384250145` martwa, `1768946230585` enforcer
co ~10 min całą noc, `1700832676138` o 3:00) — po reanimacji martwych czujników (patrz
wpis wyżej) trzy z nich ożyją naraz i zaczną się ścigać. `1768946230585` ma dodatkowo
trigger `time_pattern /5` obok krawędziowego `off→on`, więc mimo aliasu „5 minut po
aktywacji" gasi światła cyklicznie całą noc — do decyzji, czy to zamierzone.
**Fix (projekt, nie one-liner)**: skonsolidować do jednego `input_select.tryb_domu` +
jednej automatyzacji „nocne domknięcie" z jasnym priorytetem trybów i enforcerem
(cykliczny dozorca vs. jednorazowe zadziałanie po aktywacji — decyzja pkt 7), plus
finalny/ostateczny wyłącznik nocny zastępujący dzisiejsze cztery. Zakres większy niż
fix-pack — osobny task.

---

### Executor: zepsuty JSON w `approved/` retry'owany w nieskończoność co 10s zamiast trafić do `failed/`/`rejected/`

**Data**: 2026-07-23
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: podczas testów E2E remediacji bez SSH zepsuty JSON akcji w `approved/`
(przyczyna: podstawienie zmiennej lokalnej w cudzysłowie w zagnieżdżonym heredoc przez
ssh) powodował, że executor próbował go sparsować co cykl (10s), logując "Failed to
move ... to running: Expecting value" bez końca — plik nigdy nie trafiał do
`failed/`/`rejected/`.
**Fix**: executor powinien przenosić nieparsowalny JSON do `failed/` (albo dedykowanego
`malformed/`) po pierwszej próbie, nie zostawiać go do nieskończonego retry w `approved/`.

---

### Uprawnienia `actions/` naprawione TYLKO na PIHA (ręcznie) — SOLARIA i lustro niezweryfikowane

**Data**: 2026-07-23
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: fix uprawnień `/opt/homelab/actions` (grupa `pi` + setgid, patrz Zamknięte)
zastosowany ręcznie tylko na PIHA. SOLARIA (oskar tam podobno uid 1000 — do sprawdzenia
czy problem w ogóle występuje) i lustro (repo-less, patrz osobny wpis niżej)
niezweryfikowane — remediacja na tych węzłach może paść identycznie przy pierwszej
próbie.
**Fix docelowy**: node-agent powinien sam tworzyć `dispatch/<node>/` z właściwymi
prawami (grupa współdzielona + setgid) przy starcie, żeby to nie wracało za każdym
razem jako ręczna interwencja — patrz też „Tech-debt: globalny porządek uid/gid" niżej.

---

### `deploy-local.sh` (control-plane): brak twardego checka, że `.env` istnieje i `TAILSCALE_BIND_IP` jest ustawione

**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: przy fixie bindu `operator_ui.py:18180` odkryto footgun: bez `.env`,
`docker compose` tylko OSTRZEGA i po cichu wraca do bindu `0.0.0.0` zamiast failować —
dokładnie ten sam wzorzec błędu jak historyczny bug fleet-prometheus/deploy-node.sh
(brak `--env-file`, patrz Zamknięte, commit `686aca7`), tym razem w
`deploy-local.sh`/control-plane.
**Fix**: dodać twardy check w `deploy-local.sh` — `.env` musi istnieć i
`TAILSCALE_BIND_IP` musi być ustawiony, inaczej abort przed `docker compose up`.

---

### `operator_ui.py` nie ma ŻADNEJ autoryzacji

**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: po zamknięciu publicznego bindu 18180 (patrz Zamknięte, commit `9a5c160`)
`operator_ui.py` nadal przyjmuje `/action/mutate` (w tym przejście do `approved`) bez
żadnego uwierzytelnienia — chroni tylko granica Tailscale mesh, nie autoryzacja per
operator.
**Fix**: osobny temat — do zaprojektowania (token/basic auth/mTLS w mesh), poza
zakresem fixu bindu.

---

### `alert_only` zapycha approval queue

**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: przy reconie stanu wyjściowego 16 z 18 pending actions to były
liveness-transitions typu `alert_only`, nie realne decyzje do klikania — operator musi
przewijać szum, żeby znaleźć akcje, które faktycznie wymagają Approve/Reject.
**Fix**: rozważyć osobny widok/filtr dla `alert_only` w operator UI, albo
auto-acknowledge bez wejścia do tej samej kolejki co `container_restart`/`redeploy`.

---

### Crash-loop nie generuje `container_restart` — gap detekcja→akcja

**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: `node_exporter` na PIHA miał 1048 restartów i event `[high]`, ale
supervisor nie wygenerował żadnej akcji `container_restart` — crash-loop widoczny w
evencie nie przekłada się na akcję remediacyjną.
**Fix**: zbadać routing supervisora dla crash-loop sygnałów (`disk_pressure` i
`containers_not_running` mają jasne mapowanie na akcje w CLAUDE.md — crash-loop
najwyraźniej nie).

---

### Telegram yes/no dla pending actions brakuje

**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: brain-watchdog ma `send_telegram` (używane dla alertów Prometheus), ale
nic nie łączy go z `actions/pending` — operator musi wejść do operator UI, żeby
zatwierdzić/odrzucić akcję, zamiast dostać yes/no bezpośrednio na Telegramie.
**Fix**: rozszerzyć brain-watchdog (albo osobny konsument) o powiadomienie z
przyciskami approve/reject per pending action.

---

### `homeassistant5` na PIHA: `Exited (0)`, nie podnosi się mimo `restart: unless-stopped`

**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: kontener `homeassistant5` (legacy HA "ken", patrz cutover
`kb/phases/backlog.md` sekcja "Cutover HA ken") zaobserwowany w stanie `Exited (0)` — exit
code 0 = czyste zatrzymanie, więc Docker `restart: unless-stopped` świadomie go nie
podnosi (to nie crash). Do zweryfikowania, czy to zamierzone wygaszenie z cutoveru czy
coś zatrzymało kontener niezamierzenie.
**Fix**: sprawdzić czy to spójne z planem wygaszenia `homeassistant5` (patrz cutover
HA ken, krok 2 „Wygaszenie homeassistant5") — jeśli tak, brak akcji; jeśli nie,
zbadać czemu wyszedł z kodem 0.

---

### lustro: `node_agent.py` wymaga ręcznego wypchnięcia do `/opt/homelab/deploy/node-agent` (repo-less)

**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: węzeł lustro nie ma repo/gita — `node_agent.py` (z fixami remediacji bez
SSH, commit `2dac154`) musi być ręcznie skopiowany do
`/opt/homelab/deploy/node-agent`, żeby dotrzeć na ten węzeł. Ryzyko rozjazdu
repo↔runtime identyczne jak przy innych repo-less węzłach.
**Fix**: ustalić mechanizm dystrybucji kodu node-agenta na węzły repo-less (rsync przy
deployu / paczka artefaktu / inny kanał) zamiast ręcznego kopiowania.

---

### 🔴 npm@VPS panel admina :81 publicznie osiągalny z internetu

**Data**: 2026-07-10
**Źródło**: sesja `scripts/npm/npm_api.py` (skrypt do zarządzania NPM przez REST API)
**Problem**: `services/npm/docker-compose.yml` mapuje `81:81` bez ograniczenia do
interfejsu — Docker bindem domyślnym wystawia to na `0.0.0.0`, czyli panel admina
NPM@VPS jest osiągalny z publicznego IP (`135.181.153.108:81`), nie tylko przez
Tailscale mesh (`100.95.58.48:81`). Panel admina (login+hasło, bez 2FA wymuszonego)
nie powinien być publiczny. Brak `hosts/vps/runtime/npm/docker-compose.override.yml`
ograniczającego bind.
**Fix**: dodać override z bindem `127.0.0.1:81:81` (dostęp tylko przez Tailscale/SSH
tunnel) albo `<tailscale_ip>:81:81`, zachowując `80`/`443` publiczne (to jest ich rola).
Zweryfikować po zmianie, że `npm_api.py --npm vps` nadal łączy się przez
`100.95.58.48:81`.

---

### Ghosty B WRÓCIŁY — hash-prefixed control-plane na VPS nieposprzątane

**Data**: 2026-07-06
**Źródło**: sesja 2026-07-06 (`docs/sessions/2026-07-06.md`) — snapshot panelu agents.okit.pl
**Problem**: hash-prefixed kontenery control-plane znów widoczne na VPS —
wpis „ZNIKNĘŁY (potwierdzone reconem 2026-07-02)" w Zamkniętych zdezaktualizowany.
**Fix**: ręczny `docker rm` hash-prefixed kontenerów control-plane na VPS;
przy okazji sprawdzić, skąd wróciły (fix A `3b71707` miał blokować źródło divergence).
**Update 2026-07-16** (sesja `docs/sessions/2026-07-16.md`): po odetkaniu supervisora
(event flood + pętla zamrożona, oba naprawione dziś) potwierdzone, że wpisy `error`
w topologii panelu to dokładnie te ghosty — hash-prefixed w world_state observera,
NIE żywe kontenery (`docker ps -a exited=0` na VPS). Observer nie prune'uje wpisów
po zniknięciu kontenerów spod tych nazw. To trzyma `System Status: ERROR` fałszywie.
**Fix (do zrobienia)**: observer powinien weryfikować faktyczny stan kontenera przy
budowaniu world_state i prune'ować wpisy dla kontenerów, których już nie ma (ta sama
klasa błędu co „Rozjazd world-state observera: NOMINAL przed istnieniem" niżej).

---

### shadow_mode → remediacja: decyzja o auto-restart

**Data**: 2026-07-16
**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane
**Problem**: po odetkaniu supervisora (event flood + pętla zamrożona, oba naprawione
dziś) kolejka akcji nadal pusta częściowo dlatego, że `shadow_mode=True` downgrade'uje
HA `container_restart` do `alert_only` — supervisor widzi problem, ale świadomie nie
enqueue'uje akcji restartu.
**Do decyzji**: czy i kiedy włączyć auto-restart padłych kontenerów — wymaga
architektury (guardraile, cooldowny, blast radius per serwis) zanim `shadow_mode=false`.
Patrz też istniejący wpis „ha-diag-agent deploy ZABLOKOWANY" niżej — przed
`shadow_mode=false` tam wymieniony konkretny target (`homeassistant5`).

---

### gokapi: deploy-node VPS rzuca błąd — brakujący `.env`

**Data**: 2026-07-16
**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane
**Problem**: `deploy-node.sh` na VPS rzuca błąd na serwisie gokapi z powodu brakującego
`.env` (`/opt/homelab/config/gokapi/.env` nieutworzony lub niepełny — wzorzec z sesji
2026-07-09 `docs/sessions/2026-07-09-kb-configi-gokapi.md`).
**Fix**: sprawdzić `services/gokapi/env.example`, utworzyć/uzupełnić `.env` na VPS wg
konwencji `env.example` → `/opt/homelab/config/<service>/.env`.

---

### elasticsearch + diskover w stanie error na PIHA — observer zna usunięte serwisy

**Data**: 2026-07-06
**Źródło**: sesja 2026-07-06 (`docs/sessions/2026-07-06.md`) — snapshot panelu agents.okit.pl
**Problem**: elasticsearch i diskover usunięte z PIHA w module 0 (2026-07-02),
ale observer wciąż je zna i raportuje `error` w panelu. World-state nie zapomina
serwisów, które przestały istnieć — ta sama klasa błędu co „NOMINAL przed
istnieniem" (patrz wpis z 2026-06-25).
**Fix**: wyczyścić martwe wpisy z world_state / dodać wygaszanie serwisów
nieobecnych w desired state i w dockerze.

---

### Gotchas (z 2026-06-30 — migracja kapala.org → Cloudflare/wildcard)

**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)

- **NPM custom WS config + Websockets Support:** NIE wklejać
  `proxy_http_version 1.1;` do Advanced gdy Websockets Support = ON.
  NPM dodaje tę dyrektywę sam → duplikat → nginx -t failuje → plik
  proxy_host/<id>.conf się NIE generuje → "unrecognized name" mimo
  dobrego certu. Objaw mylący (wygląda jak problem certu/DNS).
  Diagnoza: `strings /data/database.sqlite | grep <domena>` → pole nginx_err.

- **Cloudflare auto-proxy na import:** CF proxuje A/CNAME przy dodaniu strefy.
  DKIM CNAME (fm1/2/3._domainkey) proxied = zepsuty podpis maila.
  Zawsze przełączyć na DNS only (szara chmurka) przed aktywacją.
  Reserved/CGNAT IP (Tailscale 100.x) CF wymusza DNS only automatycznie.

---

### Migracja okit.pl → Cloudflare (większy projekt, firmowa domena)

**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
Naprawi wszystkie wygasłe certy okit.pl naraz (HTTP-01 failuje przy mesh DNS):
ap, audiobooks, code-server, dysk, forgejo, ha-embed, hagc, ngpm, node-red,
okit.pl, pihole, ha.okit.pl. Wzorzec jak kapala.org: NS na CF, wildcard *.okit.pl
przez DNS-01, przepiąć hosty. UWAGA: okit.pl ma usługi publiczne (foty) —
rozdzielić mesh-only od publicznych. Ostrożnie — firmowa domena.

---

### foty.kapala.org renew failuje (#47, expired 2026-06-19, HTTP-01)

**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
Foty są PUBLICZNE celowo (zewn. dostęp za hasłem NPM) — port 80 powinien
być dostępny, więc HTTP-01 powinno działać. Sprawdzić czemu failuje
(DNS foty wskazuje na zły IP? port 80 zablokowany?). NIE przenosić na mesh.

---

### Cleanup po błędnej ścieżce HA-Tailscale-addon (z 2026-06-29)

**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
- ha-ken: Stop + Uninstall add-on Tailscale
- Tailscale admin: usunąć node ha-ken (100.98.128.40)
- 42.pl/okit.pl: usunąć rekord ha-ken → 87.205.110.38 (jeśli jest)

---

### Stary ha.okit.pl (cert wygasł 6/28, teraz "Not Used")

**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
Zostawić lub usunąć proxy host + DNS. Niepilne (martwy, nie szkodzi).

---

### Stopniowa migracja usług domowych okit.pl → kapala.org (mesh-only)

**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
dysk, audiobooks, budget, code-server, home, ha-embed, node-red... wg wzorca
migracji usługi na kapala.org (patrz sesja: CF rekord A → 100.108.208.3 DNS only,
NPM proxy host z wildcard *.kapala.org, Advanced PUSTE, weryfikacja grep+curl).

---

### `deploy-node.sh` nie reloaduje config-driven serwisów (cicha rozbieżność deploy↔config)

**Data**: 2026-06-26
**Źródło**: sesja 2026-06-26 (`docs/sessions/2026-06-26.md`)
**Problem**: po zmianie `prometheus.yml` (bez zmiany obrazu) `deploy-node.sh` NIE
recreate'uje kontenera. Compose widzi "kontener działa, obraz ten sam" → zostawia
Running, NIE podmienia configu → Prometheus trzyma stary config w pamięci. **Deploy
raportuje green, a zmiana configu nie wchodzi w życie.** Dziś wymagało ręcznego
`docker compose ... up -d --force-recreate`. Dotyczy KAŻDEGO serwisu config-driven bez
zmiany obrazu (nie tylko Prometheus).
**Fix**: po zmianie configu serwisu albo `--force-recreate`, albo POST `/-/reload` dla
serwisów z lifecycle API (fleet-prometheus ma `--web.enable-lifecycle`). Rozważyć
wykrywanie zmiany plików config w deploy i wymuszanie recreate.

---

### Zbadać: chelsty + chelsty-infra `node_exporter` DOWN z VPS

**Data**: 2026-06-26
**Źródło**: sesja 2026-06-26 (`docs/sessions/2026-06-26.md`)
**Problem**: przy inwentaryzacji targetów floty do fleet-prometheusa, `node_exporter`
na chelsty i chelsty-infra był nieosiągalny z VPS — pominięte w scrape. To LTE edge
(intermittent uplink), więc DOWN może być normą, ale wymaga rozróżnienia: brak
node_exportera vs odcięty uplink vs zablokowany port.
**Fix**: ustalić, czy node_exporter w ogóle działa na obu chelsty (compose/proces),
czy jest osiągalny po Tailscale z VPS, i czy ma sens go scrape'ować mimo LTE
(prawdopodobnie tak — `up==0` na LTE = sygnał dla anomaly detection, nie fałszywy alarm).

---

### Supervisor nie enqueue'uje akcji remediacji przy `error`-state

**Data**: 2026-06-25 (powtórka sygnału z 2026-06-19)
**Źródło**: sesja 2026-06-25 (`docs/sessions/2026-06-25.md`)
**Problem**: Action Queue pusta mimo `System Status ERROR` widocznego w panelu.
Supervisor nie generuje `container_restart` / `redeploy` dla serwisów w stanie `error`.
Objaw zaobserwowany co najmniej dwukrotnie — wymaga izolowanego dochodzenia.
Podejrzane: supervisor może nie reagować na error-state jeśli źródłem są ghost kontenery
(błędne project-name), nie realne health-check failures.
**Fix**: zbadać osobno — sprawdzić, czy supervisor otrzymuje właściwe eventy od observera,
czy ma własną logikę de-duplifikacji blokującą enqueue.
**Update 2026-07-02**: ghost kontenery (bug B) zniknęły z VPS — jeśli objaw wróci,
hipoteza "źródłem są ghosty" jest już nieaktualna. UWAGA: ślepy supervisor na SATURN
(brak mountu repo, patrz sesja 2026-07-02) to INNY przypadek — nie mylić z tym bugiem.
**Update 2026-07-16** (sesja `docs/sessions/2026-07-16.md`, druga połowa dnia): dwie
głębsze przyczyny pustej kolejki znalezione i naprawione (petla supervisora zamrożona
~24h — patrz „Supervisor: pętla zamrożona…" w Zamkniętych; event flood 358k plików
paraliżujący reconcile — patrz „Event flood…" w Zamkniętych). Po obu fixach supervisor
tika i reconcile się kończy, ALE objaw z tego wpisu (brak `redeploy` mimo widocznego
`error` — elasticsearch/diskover na piha, ollama solaria) **nadal aktualny** — drift→action
nie domyka się mimo odetkanego mózgu. Zostaje otwarte jako osobne dochodzenie.

---

### Rozjazd world-state observera: panel pokazuje serwis NOMINAL przed jego istnieniem

**Data**: 2026-06-25
**Źródło**: sesja 2026-06-25 (`docs/sessions/2026-06-25.md`)
**Problem**: panel wykazał `fleet-prometheus` jako nominal na SOLARII zanim kontener
w ogóle istniał — observer `world_state` rozjechany z dockerem. Artefakt rejestracji
w manifeście bez realnego kontenera. Podobna klasa błędu jak ghost kontenery.
**Fix**: observer powinien weryfikować faktyczny stan kontenera przy budowaniu world_state
zamiast opierać się wyłącznie na zarejestrowanych serwisach.

---

### 🔴 BLOKUJĄCE — FLOTA-BOMBA: node-agent SSH mount ślepy po recreate

**Data**: 2026-06-11
**Źródło**: sesja lustro ssh shipping fix
**Problem**: solaria/piha/chelsty to stare **root** kontenery node-agenta (piha Created
2026-05-27, uid 0) — sprzed dodania `user: "1000:1000"` do bazowego compose. Ich override
montuje klucz SSH w `/root/.ssh`, co działa tylko dla uid 0. Pierwszy `--force-recreate` /
reboot hosta / update obrazu przełączy kontener na uid 1000 (`homelab`, HOME=/home/homelab)
i shipping eventów na VPS padnie z "Permission denied" — dokładnie jak na lustrze
(naprawione `a5a1352`). `ssh` w `_ship_events_to_vps()` nie ma `-i` i szuka klucza
w `$HOME/.ssh`.
**⚠️ NIE RECREATE node-agenta na solaria/piha/chelsty przed fixem.**
**Fix**: ujednolicić mount → `/home/homelab/.ssh` we wszystkich
`hosts/*/runtime/node-agent/docker-compose.override.yml` (wzór: `hosts/lustro/`)
ALBO dodać `-i $HOME/.ssh/id_rsa` w `_ship_events_to_vps()`.

---

### ha-diag-agent deploy ZABLOKOWANY (placeholder token)

**Data**: 2026-06-11
**Źródło**: sesja — deploy config merged (`5e9db5c`), `.env` na piha utworzony
(`/opt/homelab/config/ha-diag-agent/.env`, chmod 600) ale token = PLACEHOLDER.
**Blokada**: chelsty-ha offline → brak tokenu i połączenia.
**Do decyzji**: cel HA — chelsty-ha vs HA Ken (`homeassistant5` na piha; z kontenera
NIE `localhost`).
**Przed `shadow_mode=false`**: target restartu w supervisorze = nazwa kontenera
`homeassistant5`; curl endpointu HA z tokenem = HTTP 200.

---

### observer-poison-quarantine — review brancha (`78c9e4a`)

**Data**: 2026-06-11
**Źródło**: sesja — patch Codexa zachowany na `task/observer-poison-quarantine`, NIE w master.
**Do zrobienia**: zweryfikować, czy observer realnie wiesza się na malformed evencie
(poison NIE był przyczyną awarii lustra — hipoteza niezweryfikowana, obalona przez
verify-before-fix). Realny bug → merge; inaczej → drop brancha i worktree.

---

### node_agent.py — drobne sprzątanie shippingu

**Data**: 2026-06-11
**Źródło**: sesja lustro ssh shipping fix
1. **Stale komentarz** `node_agent.py:546-548` — twierdzi, że kontener "runs as root";
   nieaktualne od `user: "1000:1000"`.
2. **Sukces shippingu na `logger.debug`** → podnieść do `info` lub dodać licznik —
   działający shipping jest niewidoczny w logach przy INFO, co utrudniało diagnozę
   (cicha awaria wyglądała identycznie jak ciche działanie).

---

### event-bloat: wyczyścić spłynięty backlog lustro na VPS

**Data**: 2026-06-11
**Źródło**: sesja — po fixie shippingu 7600+ plików backlogu spłynęło do
`/opt/homelab/events/lustro/` na VPS.
**Fix**: wyczyścić stare pliki (observer już je przetworzył); docelowo polityka retencji
w event-store.

---

### rsync `--omit-dir-times` (node-agent)

**Data**: 2026-06-09
**Źródło**: flota recovery session
**Objaw**: rsync exit code 23 po każdym push — `set-times` na katalogu `/opt/homelab/events/`
zwraca EPERM (oskar nie jest właścicielem katalogu; aerbot jest). Pliki są kopiowane poprawnie,
ale exit 23 zaśmieca logi i może maskować prawdziwe błędy.
**Fix**: dodać `--omit-dir-times` do wywołania `rsync` w `node-agent.py`.
**Lokalizacja**: `services/node-agent/src/node_agent.py` — wywołanie rsync w pętli push.
**Update 2026-06-11**: potwierdzone flotowo — każdy node loguje fałszywe
"Event shipping failed" (rsync code 23) co cykl, mimo że pliki przechodzą; katalogi
`/opt/homelab/events/*` na VPS należą do `aerbot`, klient nie ustawi na nich czasów.

---

### Deklaratywny zapis `oskar ∈ aerbot` w manifeście VPS

**Data**: 2026-06-09
**Źródło**: flota recovery — root cause: oskar spoza grupy aerbot(1000) → rsync Permission denied
**Problem**: przynależność do grupy jest zarządzana ręcznie (`usermod -aG 1000 oskar` ad-hoc).
Brak gwarancji po przeinstalowaniu VPS lub zmianie usera.
**Fix**: dodać do `hosts/vps/host.yaml` lub `hosts/vps/capabilities.yaml` sekcję
`users: oskar: groups: [aerbot]` — i wyegzekwować w deploy/bootstrap skrypcie VPS.
Alternatywa: zmienić właściciela `/opt/homelab/events/` na `oskar:oskar` i zaktualizować
node-agent deploy skrypty.

---

### Rozdzielenie worktree per task (agent.sh)

**Data**: 2026-06-09
**Źródło**: sesja — `homelab-codex-ws-node-onboarding` używany raz dla `task/node-onboarding`,
raz dla `task/fix-event-bloat` przez ręczne `git checkout`.
**Problem**: jeden worktree współdzielony przez dwa branche = anty-wzorzec. `git branch`
mogło wskazywać zły branch; `+` w listingu = pozornie "w innym worktree" ale nieprawda.
Prowadzi do commitowania na złej gałęzi.
**Fix**: egzekwować — jeden task = jeden worktree (`agent.sh new <task-name>`). Przy wejściu
do worktree zawsze `git branch --show-current` i weryfikacja `.agent-task`.
Długoterminowo: `agent.sh new` powinien odmawiać jeśli żądana gałąź jest już sprawdzona.

---

