From e87bef4cf24a689ec8169cfb72478ec31ec246af Mon Sep 17 00:00:00 2001 From: oskar Date: Tue, 4 Aug 2026 15:09:40 +0200 Subject: [PATCH] feat(kb): SPLIT backlog.md (72 KB) -> 19 dokumentow + cienki indeks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rozstrzygniecie 1. Monolit mieszal cztery typy OKF. Rozbity PER TYP po granicy sekcji `##`: 10 x decision — pozycje backlogu (w tym backlog-aktywne 28 KB i backlog-zamkniete 12 KB, ktore zostaja calosciami) 7 x incident — bugi/awarie dotad wtopione w backlog: cutover HA ken, checkpoint observera, ha-diag-agent node=unknown, deploy-local ghost-kontenery, paperless-worker config, deploy-node nie przebudowuje obrazu, ollama bez sterownika 2 x phase — HA configs-as-code, monitoring floty Prometheus kb/phases/backlog.md zostaje jako cienki indeks (type: phase, status: active): oryginalna preambula + wygenerowany spis linkow do wszystkich 19 elementow. 23 przychodzace odwolania zostaja przepiete na ta sciezke w grupie 7. NIE rozbijano po `###` (38 pozycji w "Aktywne" + 13 w "Zamkniete" = 51 plikow). Rozstrzygniecie mowi "rozbij per typ", a nie per pozycja; rozdrobnienie do 51 plikow rozerwaloby czytelnosc backlogu. Kontrola: preambula + 19 sekcji == oryginal z HEAD (multizbior niepustych linii). Tresc pozycji nietknieta. Co-Authored-By: Claude Opus 5 (1M context) --- docs/backlog.md | 1354 ----------------- .../2026-07-31-kb-f4-final-narty27.md | 9 + kb/decisions/backlog-aktywne.md | 625 ++++++++ .../backlog-anomaly-detection-liveness.md | 29 + kb/decisions/backlog-deploy-host-pusty.md | 19 + .../backlog-deploy-runner-instalacja.md | 41 + kb/decisions/backlog-disk-cleanup-zepsuty.md | 24 + kb/decisions/backlog-followupy-etap0.md | 22 + .../backlog-kb-serwisy-poza-monitoringiem.md | 30 + .../backlog-m1-solaria-prune-mitigation.md | 31 + .../backlog-rozjazdy-repo-rzeczywistosc.md | 83 + kb/decisions/backlog-uid-gid-flota.md | 43 + kb/decisions/backlog-zamkniete.md | 223 +++ ...2026-07-12-deploy-local-ghost-kontenery.md | 28 + ...26-07-12-observer-checkpoint-leksykalny.md | 50 + .../2026-07-12-paperless-worker-config.md | 51 + .../2026-07-14-ha-diag-agent-node-unknown.md | 48 + ...-15-deploy-node-nie-przebudowuje-obrazu.md | 35 + ...26-07-16-ollama-solaria-brak-sterownika.md | 40 + .../2026-07-22-ha-ken-cutover-legacy.md | 50 + kb/phases/backlog.md | 71 + kb/phases/ha-configs-as-code.md | 23 + kb/phases/monitoring-floty-prometheus.md | 53 + 23 files changed, 1628 insertions(+), 1354 deletions(-) delete mode 100644 docs/backlog.md create mode 100644 kb/decisions/backlog-aktywne.md create mode 100644 kb/decisions/backlog-anomaly-detection-liveness.md create mode 100644 kb/decisions/backlog-deploy-host-pusty.md create mode 100644 kb/decisions/backlog-deploy-runner-instalacja.md create mode 100644 kb/decisions/backlog-disk-cleanup-zepsuty.md create mode 100644 kb/decisions/backlog-followupy-etap0.md create mode 100644 kb/decisions/backlog-kb-serwisy-poza-monitoringiem.md create mode 100644 kb/decisions/backlog-m1-solaria-prune-mitigation.md create mode 100644 kb/decisions/backlog-rozjazdy-repo-rzeczywistosc.md create mode 100644 kb/decisions/backlog-uid-gid-flota.md create mode 100644 kb/decisions/backlog-zamkniete.md create mode 100644 kb/incidents/2026-07-12-deploy-local-ghost-kontenery.md create mode 100644 kb/incidents/2026-07-12-observer-checkpoint-leksykalny.md create mode 100644 kb/incidents/2026-07-12-paperless-worker-config.md create mode 100644 kb/incidents/2026-07-14-ha-diag-agent-node-unknown.md create mode 100644 kb/incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md create mode 100644 kb/incidents/2026-07-16-ollama-solaria-brak-sterownika.md create mode 100644 kb/incidents/2026-07-22-ha-ken-cutover-legacy.md create mode 100644 kb/phases/backlog.md create mode 100644 kb/phases/ha-configs-as-code.md create mode 100644 kb/phases/monitoring-floty-prometheus.md diff --git a/docs/backlog.md b/docs/backlog.md deleted file mode 100644 index 0431cf1..0000000 --- a/docs/backlog.md +++ /dev/null @@ -1,1354 +0,0 @@ -# Tech-debt backlog - -Centralny tracker tech-długu i znanych usterek. Wpisy ze sesji — dodawaj z datą i kontekstem. - ---- - -## M1 aktywna na SOLARII — cleanup node-agenta wyłączony do czasu R1 (2026-08-04) - -**Data**: 2026-08-04 -**Źródło**: `docs/incidents/2026-07-30-ollama-solaria-vanish.md` (§7, M1). -**Stan**: w `hosts/solaria/runtime/node-agent/docker-compose.override.yml` ustawiono -`NODE_TYPE=lte_node` (było `ai_node`) na czas backfillu embed (faza mailowa KB). -`lte_node` powoduje wczesny return w `run_safe_cleanup()`, więc na SOLARII nie działa -niefiltrowany `docker container prune` kasujący zatrzymane kontenery w ≤60 s — -łącznie z tymi, które mają `restart: unless-stopped` i zostały zatrzymane świadomie. -`self.node_type` jest czytane wyłącznie w `run_safe_cleanup()` i dwóch liniach logu, -więc monitoring, eventy i dispatch akcji działają bez zmian. -**Skutek uboczny**: `lte_node` pomija CAŁY cleanup — na czas mitygacji nie są sprzątane -dangling images ani build cache. Pilnować miejsca na dysku SOLARII. -**Nie zdeployowane** — zmiana jest tylko w repo (branch `task/m1-prune-mitigation`); -wchodzi przy najbliższym redeployu node-agenta na SOLARII. -**Zdjąć po**: wdrożeniu R1 (filtrowanie prune po restart policy / labelu compose) na tym -nodzie — wtedy przywrócić `NODE_TYPE=ai_node`. R1–R3 w toku po stronie subsystemu A. - ---- - -## deploy-runner: instalacja na węzłach + E2E redeployu (2026-08-03) - -**Data**: 2026-08-03 -**Źródło**: sesja `task/redeploy-fix` — recon D14/D15 + OPEN QUESTION 4 (nieopróżniona -kolejka akcji). -**Było**: `redeploy` nie mógł się wykonać nigdy. Executor odpalał -`scripts/deploy/deploy-node.sh ` **wewnątrz swojego kontenera** — -skrypt ignoruje oba argumenty i wymaga repo w `${HOME}/homelab-codex-ws` -(w kontenerze `HOME=/home/homelab`, katalog nie istnieje) → `exit 1` w 18. linii. -Za tym stały jeszcze trzy blokady: brak `git`, brak klienta `docker` w obrazie oraz -— gdyby przeszedł — deploy **całego zestawu usług hosta executora**, nie węzła -z akcji. Stąd `healthcheck_failed` przekierowany 2026-07-29 na `container_restart` -i 18 pending / 0 completed. -**Zrobione w repo (ta sesja)**: `scripts/deploy/deploy-service.sh` (deploy jednej -usługi, wspólny z `deploy-node.sh` — ta sama inwokacja compose, więc ta sama nazwa -projektu), `jobs/deploy-runner/` (systemd na hoście: rsync-pull akcji, walidacja, -deploy, `action_result` z powrotem), executor dispatchuje `redeploy` do -`actions/deploy//` i rozlicza je jak `container_restart` -(`REDEPLOY_TIMEOUT_SECS=900`). 248 testów zielonych. -**Do zrobienia (runtime, wymaga operatora)**: -1. Deploy control-plane na VPS (nowy executor + `../..:/repo:ro`). -2. Instalacja `jobs/deploy-runner/` na vps, piha, solaria — patrz README - („Install (per node)"). Na VPS `VPS_EVENTS_HOST` musi zostać puste. -3. E2E na benignej usłudze na PIHA (wzorzec `test-e2e-b` z 2026-07-23), potem - opróżnienie kolejki 18 pending — w tym `redeploy-vps-gokapi`. -4. SOLARIA: `group_add: "996"` dla node-agenta wciąż niewdrożony (recon 641-649) — - dopóki nie wejdzie, `container_restart` tam nie działa (redeploy działa, bo nie - idzie przez node-agenta). - ---- - -## disk_cleanup w executorze jest zepsuty tak samo jak był redeploy (2026-08-03) - -**Data**: 2026-08-03 -**Źródło**: sesja `task/redeploy-fix` (znalezione przy okazji, poza zakresem zadania). -**Problem**: `executor._execute_disk_cleanup()` odpala `ssh oskar@ …`, a obraz -control-plane (`python:3.11-slim` + `pip install pyyaml`) **nie ma klienta ssh** — -`subprocess.run(["ssh", …])` leci `FileNotFoundError`, akcja ląduje w `failed/`. -To ta sama klasa błędu co redeploy i sprzeczne z decyzją „bez SSH w executorze". -**Fix**: przenieść `disk_cleanup` na model dispatch (node-agent albo deploy-runner — -runner ma już hosta i uprawnienia) albo usunąć typ akcji. Do decyzji przy okazji -opróżniania kolejki. - ---- - -## `scripts/deploy/deploy-host.sh` to pusty plik (2026-08-03) - -**Data**: 2026-08-03 -**Źródło**: sesja `task/redeploy-fix`. -**Problem**: 0-bajtowy, wykonywalny stub w `scripts/deploy/` — wygląda jak -entrypoint, nie robi nic. Kandydat do skasowania (jak `deploy-role.sh` w etapie 0). - ---- - -## Cutover HA "ken": kontener piha to legacy, prawdziwy dom to RPi4/HAOS (2026-07-22) - -**Data**: 2026-07-22 -**Źródło**: recon — dwie instancje HA równolegle sterowały domem (kontener -`homeassistant5` na piha + RPi4 HAOS 192.168.31.7), patrz -`services/home-assistant/DESIGN.md` sekcja "Incident log". `instances.yaml` -naprawiony w tej samej sesji: `ken` = 192.168.31.7 (api), `ken-legacy` = -dawny kontener piha (docker-exec, archived). - -**Do zrobienia**: -1. **ha-diag-agent na piha**: przepiąć z `http://localhost:8123` (celuje w - legacy!) na `http://192.168.31.7:8123` — wymaga nowego tokenu - `diag_agent` wystawionego na instancji 31.7 (obecny token jest dla - kontenera piha i nie zadziała na nowym targecie). -2. **Wygaszenie `homeassistant5`**: import archiwalny do - `services/home-assistant/config/ken-legacy/` → `docker stop` (BEZ `rm`) - → 7 dni obserwacji (upewnić się, że nic w domu nie polega na tym - kontenerze) → decyzja o `docker rm`. -3. ✅ ZROBIONE (2026-07-22) — **Adapter `api` w `import.sh` dla `ken`**: - automatyzacje/skrypty/sceny przez `/api/config//config/` - (REST), dashboardy/area+entity registry/`input_*` helpery przez websocket - API (`scripts/ha/lib/ha_api.py`, `ha_ws.py`, `import_api.py`). Pierwszy - realny import `ken` zaimportował 118 automatyzacji, 5 skryptów, 3 sceny, - 7 dashboardów (default + 6 named; jeden zarejestrowany dashboard nigdy - nie skonfigurowany — `config_not_found`, odnotowany w raporcie, nie - twardy błąd). Pełny import `/config` pozostaje poza zasięgiem (HAOS bez - SSH) — patrz DESIGN.md. -4. ✅ ZROBIONE (2026-07-22) — **`scripts/ha/deploy.sh`, adapter `api`, zakres - automations/scripts/scenes**: drift-check (świeży re-import vs. `HEAD`, - dowolna różnica poza plikami z tego deployu = abort z diffem) → - walidacja (lokalny sanity check + `check_config` na instancji) → zapis - per obiekt (`POST /api/config//config/`) → verify (GET + - porównanie, bez auto-rollbacku). `--dry-run` zweryfikowany na żywym - `ken` (read-only, bez różnic). Testy offline: - `scripts/ha/tests/test_deploy_api_offline.sh`. Poza zakresem: dashboardy/ - helpery (WS, brak mutującej komendy), adapter `docker-exec`, DELETE - obiektów usuniętych z repo (tylko ostrzeżenie). - ---- - -## Nowy podprojekt: Home Assistant configs-as-code (szkielet) - -**Data**: 2026-07-21 -**Branch**: `task/ha-skeleton` - -Szkielet struktury dla `services/home-assistant/` — configs-as-code dla -instancji HA (`ken` na PIHA, `chelsty-ha`). Na razie tylko struktura + -read-only import (`scripts/ha/import.sh`), bez deployu. Fazowanie, wybór -adaptera per instancja, model sync i otwarte pytania — -`services/home-assistant/DESIGN.md`. - ---- - -## Plan: Monitoring floty — Prometheus jako źródło prawdy - -**Data**: 2026-06-22 -**Źródło**: sesja 2026-06-22 (`docs/sessions/2026-06-22.md`) -**Decyzja**: Prometheus (pull, `up{}`) zastępuje warstwę WYKRYWANIA liveness -(node-agent shipper + rsync/ssh + event-store + observer prune/checkpoint + ręczne TTL) -— przyczynę nawracających awarii (uid≠1000, ślepy ssh-mount, bloat ~242k eventów, -race prune↔checkpoint, NOMINAL-bez-TTL). Osobny fleet-Prometheus pod GitOps, **nie** -adopcja domowego instance PIHA. **BEZ** Alertmanagera — alert przez brain-watchdog. -Placement: VPS. Granica: zostają supervisor (remediacja), observer/panel, ha-diag-agent, -historia incydentów, out-of-band watchdog. -> Zastępuje wcześniejszy szkic (blackbox + Alertmanager) z sesji 2026-06-17. - -**Kroki (priorytetowo)**: -1. Scaffold serwisu `fleet-prometheus` pod GitOps (worktree `task/fleet-prometheus`, - wzorzec `services/vikunja/`): compose + `env.example` + `service.yaml` + README + - `healthcheck.sh`; rejestracja w `hosts/vps/services.yaml` + `inventory/topology.yaml`; - exposure `tailscale-internal`; pusty scrape na start (self + lokalny `node_exporter` VPS). -2. ✅ ZROBIONE (2026-06-26, commit `7d4014e`) — Inwentaryzacja nodów floty `100.x` do - scrape. Dodane: piha/solaria/lustro (`node:` label), vps zachowany. Saturn pominięty - (workstation), chelsty/chelsty-infra pominięte (node_exporter down z VPS → osobny wpis). -3. Container-layer exporter (cAdvisor lub lekki docker-state) — `node_exporter` nie widzi - kontenerów. -4. ✅ ZROBIONE (2026-06-30, commit `d417000`) — Reguły liveness (`up==0 for: 5m`). - `rules/liveness.yml`: `NodeDown expr up{node=~"vps|piha"}==0 for 5m severity critical`. - Only always-on (vps, piha); solaria/lustro świadomie wykluczone (intermittent → anomaly - detection). Deploy: fleet-prometheus Recreated (zmiana compose), reguła inactive=poprawnie. -5. ✅ ZROBIONE (2026-06-30, commit `62d6fc0`) — brain-watchdog: drugie wejście — poll - Prometheus `/api/v1/alerts` (`firing`) → Telegram. Architektura A: dwa niezależne tory, - mózg NIETKNIĘTY, debounce per-alert (klucz alertname:node w state.json). 12 testów pass. - PENDING zamknięty 2026-07-02: poll POTWIERDZONY reconem (obraz zbudowany po `62d6fc0`, - `PROMETHEUS_URL` w `.env` i w env kontenera, zero poll failed) — patrz - `docs/infra/inventory-verify-2026-07-02.md`. - ✅ END-TO-END UDOWODNIONE 2026-07-06 (Etap 0 cutoveru): tor Prometheus → - watchdog → Telegram potwierdzony w produkcji testem `AlertTestEtap0` - (firing → log polla → Telegram → rollback) — patrz `docs/sessions/2026-07-06.md`. -6. Rotacja tokenu HAOS w domowym prom (plaintext). -7. Przepięcie observer / panel `agents.okit.pl` na Prometheus jako źródło — największy - znak zapytania przy cutoverze. -8. Parallel-run obok rury eventowej; cutover dopiero gdy Prometheus-truth się udowodni. - ---- - -## 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 `services/narty27/README.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//config/`) — -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 --delete ` (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//config/` 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**: `services/home-assistant/docs/audyt-automatyzacji-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**: `services/home-assistant/docs/audyt-automatyzacji-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**: `services/home-assistant/docs/audyt-automatyzacji-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**: `services/home-assistant/docs/audyt-automatyzacji-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//` 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 -`docs/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 `: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//.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/.conf się NIE generuje → "unrecognized name" mimo - dobrego certu. Objaw mylący (wygląda jak problem certu/DNS). - Diagnoza: `strings /data/database.sqlite | grep ` → 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 `). 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. - ---- - -## Zamknięte - -### Publiczny bind `operator_ui.py:18180` na VPS bez autoryzacji — NAPRAWIONE (2026-07-22, commit `9a5c160`) - -**Data**: 2026-07-22 -**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) -**Było**: `operator_ui.py` (port 18180) nasłuchiwał na `0.0.0.0` na publicznym VPS -(`135.181.153.108`) bez żadnej autoryzacji. `curl` z zewnątrz do `/actions` zwracał -HTTP 200. `do_POST /action/mutate` przenosi akcje między stanami WŁĄCZNIE z -`"approved"` — dowolna osoba z internetu mogła zatwierdzić akcję remediacyjną. -Jedyne co chroniło do tej pory: executor nie umiał jeszcze wykonać akcji (brak SSH, -patrz wpis niżej) — przypadek, nie zabezpieczenie. -**Naprawione**: dual-bind wg wzorca fleet-prometheus — `127.0.0.1:18180:8080` + -`${TAILSCALE_BIND_IP}:18180:8080`, nowy `services/control-plane/env.example`. -Zweryfikowane po deployu: publiczny IP → HTTP 000, Tailscale → HTTP 200. Konsumenty -(node-agent VPS, materializer PIHA) nietknięte. -**Footgun zapamiętany**: brak `.env` → `docker compose` tylko OSTRZEGA i po cichu -wraca do bindu `0.0.0.0`, nie failuje — patrz wpis „deploy-local.sh: brak twardego -checka .env" w Aktywnych. -**Pozostaje osobno**: `operator_ui.py` nadal bez żadnej autoryzacji (bind zamknięty -chroni przed internetem, nie przed kimkolwiek w mesh Tailscale) — patrz Aktywne. - ---- - -### Remediacja floty bez SSH: executor→node-agent pull przez rsync — ZROBIONE (2026-07-22, commit `2dac154`), E2E potwierdzone 2026-07-23 - -**Data**: 2026-07-22 (implementacja), 2026-07-23 (pierwszy udany cykl E2E) -**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) -**Było**: łańcuch supervisor→pending→approved→running działał, ale executor próbował -`ssh oskar@{node} docker restart {container}` — kontener control-plane nie ma klienta -ssh, klucza, ani rozwiązywalnych nazw węzłów. Wykonanie zawsze failowało. -**Decyzja architektoniczna**: bez SSH w executorze (kontener na publicznym VPS z -powłoką na całą flotę = zły blast radius). Kierunek PULL: executor zleca (zapis -`actions/dispatch//.json`), node-agent na docelowym węźle wykonuje -lokalnie przez własny `docker.sock`, wynik wraca eventem `action_result` przez -istniejący rsync-push. VPS nigdy nie inicjuje połączenia do węzła. -**Zabezpieczenia**: walidacja `node == self.node_name`; whitelist typów akcji = -`{container_restart}`; guard przed restartem samego node-agenta; brak wykonywania -dowolnych poleceń z payloadu; idempotencja (ponowne zlecenie = no-op). 183 testy. -**Potwierdzone w boju (2026-07-23)**: cykl `test-e2e-b` — Executing → Dispatched → -Completed w 31 sekund, `node_exporter` na PIHA realnie zrestartowany (uptime 30h→ -minuty), zero połączeń SSH. Pierwszy w historii systemu pełny cykl remediacji. - ---- - -### Uprawnienia `actions/` na PIHA (uid/gid oskar 1004 vs kontener 1000) — NAPRAWIONE ręcznie (2026-07-23, poza repo) - -**Data**: 2026-07-23 -**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) -**Było**: pierwszy test E2E remediacji bez SSH padał — agent logował `[Errno 13] -Permission denied: /opt/homelab/actions/dispatch` co cykl. `/opt/homelab/actions` -było `oskar:oskar drwxr-xr-x` (utworzone w maju), a działający wzorzec to -`/opt/homelab/events` = `oskar:pi drwxrwsr-x` (grupa `pi`, zapis grupowy, setgid). -Ten sam motyw uid/gid (host oskar 1004 vs kontener 1000) uderzył już czwarty raz — -patrz sekcja „Tech-debt: globalny porządek uid/gid/uprawnień we flocie". -**Naprawione (ręcznie, tylko na PIHA)**: `chgrp -R pi` + `chmod -R g+w` + `chmod g+s` -na `/opt/homelab/actions`. Executor zachował się poprawnie podczas awarii: po 300s -timeoutu przeniósł akcję do `failed` z czytelnym powodem, nic nie zawisło. -**Pozostaje osobno**: fix zastosowany TYLKO na PIHA — SOLARIA i lustro -niezweryfikowane; docelowy fix systemowy to node-agent tworzący -`dispatch//` z właściwymi prawami przy starcie — patrz Aktywne. - ---- - -### Supervisor: pętla zamrożona ~24h, healthy ale nie tika — NAPRAWIONE (2026-07-16, commit `409b583`) - -**Data**: 2026-07-16 -**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane -**Było**: kontener supervisora `healthy`, ale pętla `reconcile()` nie tikała od ~24h, -zero logów. Root cause zweryfikowany na `/proc` (`State:S hrtimer_nanosleep`, NIE -deadlock): `glob` po `EVENTS_DIR` co cykl przy 358k plikach → cykl przekracza brak -timeoutu → nigdy się nie kończy. Logi na DEBUG maskowały objaw. -**Naprawione**: każdy cykl w `ThreadPoolExecutor` z `future.result(timeout=90s, -env SUPERVISOR_RECONCILE_TIMEOUT)`; try/except owija cykl (wyjątek nie zabija pętli); -tick-log co 10 cykli (`SUPERVISOR_TICK_LOG_EVERY`) na INFO; healthcheck sprawdza -świeżość heartbeat, nie tylko czy proces żyje. Zweryfikowane w boju: pętla tika -(cycle #340→#480), cykl #1 timeoutował ale pętla kontynuowała = odporność działa. -**Lekcja**: „healthy kontener ≠ tikająca pętla" — healthcheck musi sprawdzać -świeżość ostatniego cyklu, nie samo czy proces odpowiada. - ---- - -### Event flood 358k plików + retencja martwa od fixu checkpointu — NAPRAWIONE (2026-07-16, commit `dff76ec`) - -**Data**: 2026-07-16 -**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane -**Było**: `EVENTS_DIR` = 358k plików, 91% to `service_healthy` emitowany co cykl per -serwis (stan-jako-zdarzenie, antywzorzec). Retencja `_cleanup_control_plane_fs()` -była martwa od fixu checkpointu `d5139c9` (2026-07-15, patrz „Bug: checkpoint -observera po ścieżce leksykalnej" niżej) — porównanie `str(ścieżka) <= checkpoint_int` -rzucało `TypeError` cicho łapany przez szeroki `except` → backlog rósł bez ograniczenia. -Naprawiając checkpoint wczoraj, złamaliśmy retencję, która na nim polegała. -**Naprawione**: (a) node-agent emituje `service_healthy` tylko na transition -unhealthy→healthy (funkcja dla `observer.process_event` zachowana); (b) retencja -naprawiona epoch-do-epoch; (c) `scripts/maintenance/cleanup_event_backlog.py` -(dry-run + `--apply`). 150 testów pass. Cleanup wykonany na PIHA+VPS: 272 232 pliki -usunięte, backlog 358k→12,7k. **Wynik**: reconcile supervisora przestał timeoutować. -**Lekcja**: migracja typu pola (ścieżka→epoch int) musi audytować WSZYSTKICH -konsumentów tego pola — szeroki `except` maskował dokładnie tę klasę regresji. - ---- - -### Ghost kontenery w panelu (Problem B) — ZNIKNĘŁY (potwierdzone reconem 2026-07-02) - -> ⚠️ ZDEZAKTUALIZOWANE 2026-07-06: ghosty znów widoczne na VPS — patrz wpis -> „Ghosty B WRÓCIŁY" w Aktywnych (`docs/sessions/2026-07-06.md`). - -**Data**: 2026-06-24 (wykryte), 2026-07-02 (zamknięte) -**Źródło**: sesje 2026-06-24/25/26; recon `docs/infra/inventory-verify-2026-07-02.md` -**Było**: martwe kontenery ze starych project-name'ów -(`8547b46c0317_control-plane-supervisor` itp.) raportowane przez observera jako -`error` → `System Status ERROR` w panelu mimo zdrowego mózgu. -**Zamknięte**: recon 2026-07-02 — 24 kontenery na VPS, ZERO hash-prefixed. -Prawdopodobnie recreate'y z kolejnych deployów je zmiotły. Zero akcji ręcznej. -Rozważenie czyszczenia obcych project-name przy deployu — już nieaktualne -(fix A `3b71707` blokuje źródło divergence). - ---- - -### brain-watchdog: poll Prometheus — POTWIERDZONY (recon 2026-07-02) - -**Data**: 2026-06-30 (pending), 2026-07-02 (zamknięte) -**Źródło**: recon `docs/infra/inventory-verify-2026-07-02.md` -**Było**: log startowy nie wypisuje `PROMETHEUS_URL` → brak pewności, że polling -aktywny; diagnoza opierała się na `.env` i braku błędów. -**Zamknięte**: recon potwierdził — obraz zbudowany po `62d6fc0`, `PROMETHEUS_URL` -w `.env` I w env kontenera, zero `poll failed` w logach. Poll aktywny. -Jednolinijkowy log startowy (`polling enabled/disabled`) pozostaje opcjonalną -kosmetyką. ✅ Pełne end-to-end (firing → Telegram) POTWIERDZONE 2026-07-06 -testem `AlertTestEtap0` (Etap 0 cutoveru, `docs/sessions/2026-07-06.md`) — -bez czekania na realną awarię. - ---- - -### Miny #1/#2/#3 z weryfikacji inwentaryzacji — ROZBROJONE (2026-07-02) - -**Źródło**: `docs/infra/inventory-verify-2026-07-02.md`, sesja -`docs/sessions/2026-07-02.md` (tam szczegóły i lekcje). -- **#1 PIHA checkout**: gałąź wciąż `task/kb-gmail-import` po resecie z 2026-06-30 - (reset --hard przesuwa gałąź, nie przełącza) → `checkout master && pull`, - 30 commitów nadrobione. -- **#2 control-plane na SATURN**: supervisor ślepy (brak mountu repo) → - `docker compose down`, wolumeny zachowane. Jedyny mózg = VPS. -- **#3 owner_node**: forgejo→piha, mosquitto→vps (commit `886bc85`). - ---- - -### 🔴 KRYTYCZNY — `deploy.sh vps` niszczył control-plane — NAPRAWIONE (commit `3b71707`) - -**Data**: 2026-06-24 (wykryte), 2026-06-25 (incydent w produkcji), 2026-06-26 (naprawione) -**Źródło**: sesja 2026-06-24 + 2026-06-25 + 2026-06-26 (`docs/sessions/2026-06-26.md`) -**Było (Problem A)**: `control-plane` w `hosts/vps/services.yaml` jako zwykły serwis -pętli `deploy-node.sh`. Pętla używała innego `COMPOSE_PROJECT_NAME` niż `deploy-local.sh` -(cwd=`services/control-plane`). Niezgodność → Recreate → `No such container: -_control-plane-observer` → `set -e` przerywa pętlę → observer/supervisor/executor/ui -znikają. Każdy `deploy.sh vps` rozkładał mózg (potwierdzone w produkcji 2026-06-25). -**Naprawione**: guard w `deploy-node.sh` pomijający serwisy z własnym -`services//deploy-local.sh`. `control-plane` ZOSTAJE w `services.yaml` (gate -pytest+build nadal go testuje), pomijana jest tylko destrukcyjna pętla deployu. -**Potwierdzone w boju**: `deploy.sh vps` wypisał `Skipping control-plane: ma własną -ścieżkę deployu`, mózg `Up 25h healthy`, nietknięty. -**Pozostało osobno**: ghosty z poprzednich rozjazdów (Problem B — patrz Aktywne). - ---- - -### Flaky testy control-plane — state-leak w pytest — NAPRAWIONE (commit `992ff7c`) - -**Data**: 2026-06-24 (zgłoszone), 2026-06-25 (naprawione) -**Źródło**: sesja 2026-06-24 + sesja 2026-06-25 (`docs/sessions/2026-06-25.md`) -**Było**: `test_incident_lifecycle.py` flaky przez state-leak — `OBSERVER_STATE_FILE` -wyprowadzany przy imporcie, helpery patchowały `STATE_DIR` ale nie `OBSERVER_STATE_FILE` → -checkpointy pisane na realny dysk `/opt/homelab/state/` z ścieżkami otagowanymi numerem -przebiegu pytest. Gate deploy.sh czerwony przy zdrowym kodzie. -**Naprawione**: autouse monkeypatch fixture redirectujący WSZYSTKIE ścieżki stanu w tym -`OBSERVER_STATE_FILE`; usunięto buggy `_make_observer`; posprzątano zatruty realny checkpoint. -Weryfikacja: 6/6 przebiegów → 28 passed. Gate rzetelny. - ---- - -### deploy-node.sh — brak `--env-file` (bind 0.0.0.0) — NAPRAWIONE (commit `686aca7`) - -**Data**: 2026-06-25 -**Źródło**: sesja 2026-06-25 (`docs/sessions/2026-06-25.md`) -**Było**: `deploy-node.sh` nie przekazywał `--env-file` do `compose up` → zmienne `.env` -(jak `TAILSCALE_BIND_IP`) interpolowały się do pustego stringa → bind `0.0.0.0` zamiast -Tailscale IP. Potencjalna dziura na publicznym VPS. -**Naprawione**: guard `if [ -f "services//.env" ]` + `--env-file` per-serwis przed -`docker compose up`. Worktree `task/deploy-envfile-fix`, merge ff-only. - ---- - -### Swap 2–4 GB na VPS — ZROBIONE - -**Data**: 2026-06-22 (zgłoszone PENDING w sesji 2026-06-09 flota-recovery) -**Źródło**: sesja 2026-06-22 (`docs/sessions/2026-06-22.md`) -**Było**: VPS (3.7 GB RAM) z `swap=0` → OOM 2026-06-01. -**Zrobione**: `/swapfile` 4 GB aktywny i trwały (wpis w `/etc/fstab`); `vm.swappiness=10` -na żywo i w `/etc/sysctl.conf`. Weryfikacja: `free -h` → `Swap: 4.0Gi (0B used)`, -`/proc/swaps` zawiera `/swapfile`. -**Uwaga**: host-level one-off (nie czysto GitOps), udokumentowany jako celowy host-state -w `hosts/vps/host.yaml`. Brak commitu kodu — zmiana host-side gotowcem. - ---- - -### Observer staleness — martwy node pokazywany NOMINAL - -**Data**: 2026-06-08 (złapane), status: OTWARTY w sensie implementacji -**Problem**: observer/supervisor trzyma ostatni znany stan; brak heartbeat TTL. -Chelsty-infra milczy, ale status NOMINAL podważa zaufanie do panelu. -**Fix**: heartbeat TTL → po przekroczeniu oznacz status `stale` lub `down`. -**Powiązane**: brain-watchdog ślepy na per-node freshness. -*(Otwarty jako TODO implementacyjny — przeniesiony z sesji 2026-06-08)* - -## Anomaly detection liveness — mózg uczy się wzorca dobowego per node (pomysł 2026-06-26) - -**Idea**: zamiast statycznych okien czasowych w regułach alertowych (np. "lustro 7-23"), -mózg (supervisor/observer) czyta historię metryk z Prometheus (range queries / Grafana) -i SAM wykrywa wzorzec dobowy każdego węzła. `up==0` zgodne z nauczonym wzorcem offline -(lustro zwykle off nocą, solaria nieregularnie) = NIE anomalia, nie alarmuj. `up==0` -odbiegające od wzorca = realna awaria → alert. Inteligencja w mózgu + dane jako źródło -wzorca, nie sztywne godziny wpisywane ręcznie. - -**Warunek**: wymaga TYGODNI historii metryk. fleet-prometheus postawiony 2026-06-25 → -realne dopiero za ~2-4 tygodnie, gdy uzbiera się wzorzec dobowy. - -**Pułapka**: uczący się system może przeoczyć realną awarię pokrywającą się z typowym -oknem offline (statyczna reguła jest głupia, ale przewidywalna). Uwzględnić przy projektowaniu. - -**Na teraz**: targety scrape'owane BEZ polityki alertowej, label tylko `node:`. Prometheus -gromadzi historię. Anomaly detection = osobny świadomy projekt później (CC, z testami). - ---- -## Rozjazdy repo<->rzeczywistosc (z inwentaryzacji 2026-06-30) -**Zrodlo**: `docs/infra/inventory-2026-06-30.md` (23 rozjazdy, pelna tabela tam). -**Weryfikacja 2026-07-02**: `docs/infra/inventory-verify-2026-07-02.md` — bilans: -20 wciaz aktualnych, 2 zmienione, 1 wyjasniony (storage SOLARIA = partycja Windows -dual-boot, NIE rozjazd — zdjety z listy). -Ponizej te wymagajace akcji, pogrupowane wg ryzyka. Naprawa = osobny task/kilka. - -### Grupa A — czyste docs, zero ryzyka -- ✅ ZROBIONE (2026-07-02, commit `886bc85`) — **forgejo** `service.yaml owner_node`: - saturn -> piha (biega na PIHA always-on) -- ✅ ZROBIONE (2026-07-02, commit `886bc85`) — **mosquitto** `service.yaml owner_node`: - piha -> vps (biega na VPS, nie na PIHA) -- **capabilities SATURN**: RAM 8 -> 14GiB; dysk sd-card 64GB -> /dev/sda 159GB -- **capabilities SOLARIA**: CPU 24 -> 32 nproc -- **`hosts/saturn/services.yaml`** nie istnieje — 5 kontenerow bez deklaracji -- **`hosts/vps/services.yaml`** niekompletne (4 z 9 z topology); **solaria** tez (brak planner-agent) - -### Grupa B — wymaga decyzji -- **npm x2**: PIHA (LAN ingress :80/:443) + VPS (public). Repo zna jedna (owner=vps). - Decyzja: zostawic oba (intentional, wildcard cert via NPM@PIHA) czy usunac PIHA? - Jesli oba zamierzone -> dodac piha do service.yaml + hosts/piha. -- ✅ ZROBIONE (2026-07-02) — **control-plane na SATURN**: `docker compose down` - (wolumeny zachowane). Supervisor byl SLEPY (brak mountu repo, WARNING loop - "Hosts directory /repo/hosts does not exist" co 30s) — zero ryzyka zdublowanych - remediacji przez te 3 dni. Jedyny control-plane = produkcyjny na VPS. -- **ollama**: `service.yaml owner=solaria` ale NIE biega. Wdrozyc czy wyrzucic z repo? - -### Grupa C — sprzatanie -- **control-plane-ui healthcheck**: uzywa `curl` ktorego NIE MA w obrazie -> failuje w - kolko -> UNHEALTHY + log spam (4.2G syslog na SATURN). Fix: wget/nc w healthcheck - albo curl w Dockerfile. (Przyczyna rozjazdu #6 znaleziona przy gaszeniu dysku.) -- **homeassistant5 na PIHA** (HA "ken" :8123) niedeklarowany -> dodac do hosts/piha + topology -- **VPS**: outline-postgres-1 anonimowy image (4e6e670bb069) -> named tag; - humanai-landing/mailer/umami do repo. ~~joplin-db postgres:18 -> 17/16~~ - (ocena zdezaktualizowana 2026-07-02: PG18 GA od 09/2025, nie pre-release — bez akcji) -- **PIHA: 33 shadow kontenery** poza GitOps (immich, vaultwarden, wikijs, actual, - audiobookshelf, elasticsearch, grafana, prom, portainer, code-server, diskover...) - -> audyt + stopniowo do hosts/piha/services.yaml -- **zigbee2mqtt** topology mowi chelsty-infra, biega na PIHA -> poprawic topology -- **stability-agent / node_exporter** owner_node single, biegaja wielomiejscowo -> per-host - -### Followupy z weryfikacji + rozbrajania min (2026-07-02) -**Zrodlo**: `docs/infra/inventory-verify-2026-07-02.md` + sesja 2026-07-02. -Zgloszone przy fixie owner_node (`886bc85`), swiadomie NIE ruszone — osobne decyzje. - -- **forgejo** brak wpisu w `hosts/piha/services.yaml`; **mosquitto** brak - w `hosts/vps/services.yaml` — schemat hostowy wymaga role/exposure/depends_on - (miny #2/#3/#16 z audytu). -- **mosquitto na VPS bez mem_limit override** w `hosts/vps/runtime/` — - narusza konwencje CLAUDE.md (kazdy serwis VPS deklaruje mem_limit). -- **drugi mosquitto na chelsty-infra** (offline'owa instancja) — pojedyncze - `owner_node` jej nie opisuje; wzorzec per-host jak stability-agent / - node_exporter (miny #17/#18). -- **topology.yaml:75**: mosquitto zadeklarowany tez jako komponent ai-cluster — - rozstrzygnac, czyj jest broker :1883. -- **pi-watchtower-1 na LUSTRO w restart-loopie** (nowe z reconu; node-agent healthy). -- **alias `lustro` nie rezolwuje z SOLARII** (nowe z reconu). -- **fleet-prometheus bez formalnego override mem_limit** w `hosts/vps/runtime/` — - limit siedzi w bazowym compose (kosmetyka). - -### Po odchudzaniu PIHA (2026-07-02, faza 2 modulu 0) -- **llm-gateway: zlokalizowac/zarchiwizowac zrodlo** — kod (wlasny FastAPI router -> - Ollama@SOLARIA) moze zyc TYLKO w `/opt/llm-gateway` na PIHA, bez gita; przeszukanie - PIHA i repo nie znalazlo innej kopii. Zarchiwizowac do repo/Forgejo zanim padnie nosnik. -- **Prometheus@PIHA: target llm-gateway blednie nazwany `watchtower`** — celuje w :8080 - i odpytuje `/v1/metrics`, dostaje wieczne 404 (llm-gateway nie serwuje metryk). - Naprawic nazwe/endpoint albo usunac target. - -### Tech debt SATURN (z gaszenia dysku 2026-06-30) -- **`/opt/anaconda3` 16G** — najwiekszy pojedynczy zjadacz dysku (env-y Pythona). Decyzja Oskara kiedy/czy czyscic. -- Dysk 91% -> 83% ugaszone (docker prune + journal + syslog), ale `/home` zaszyfrowany - i ciasny strukturalnie. SATURN dzwiga dev + drugi control-plane + agent-webui — napiecie. - -## Tech-debt: globalny porządek uid/gid/uprawnień we flocie (2026-07-10) - -**Diagnoza.** Flota NIE ma spójnej mapy uid/gid. "oskar" ma różne uid per host -(PIHA: 1004, inne hosty: prawdopodobnie 1000/inne). Kontenery agentów zakładają -uid 1000 (user "homelab"). Bind-mounty przenoszą SUROWE uid (nie nazwy) między -hostem a kontenerem → gdy uid hosta ≠ uid zakładany przez kontener, pliki stają -się "cudze" i wybucha cicha awaria (klucz nieczytelny, rsync nie tworzy plików, -socket permission denied). To NIE są przypadki — to systemowy brak kanonicznej -mapy uid/gid. - -**Historia incydentów (dowód że systemowe):** -- 2026-07-10: node-agent PIHA (uid 1000 homelab) montował /home/oskar/.ssh (pliki - uid 1004) → "Load key id_rsa: Permission denied" → rsync padał → 21 dni bez - eventów (wykryte przez shadow-read). Fix: dedykowany /opt/homelab/agent-ssh - chown 1000. -- Wcześniej: oskar spoza grupy `aerbot` na VPS → rsync push nie tworzył plików → - brak cleanup → 8-dniowa cicha awaria floty. Fix: usermod -aG aerbot oskar. -- 2026-07-10 (świeże, PENDING): node-agent PIHA "Docker unavailable: PermissionError(13)" - po recreate — agent nie czyta /var/run/docker.sock (grupa docker/uid). Osobny od - shippingu (nie blokuje eventów), ale ten sam rodzaj problemu — do naprawy - (grupa docker w kontenerze / gid socketu). -- LUSTRO uid pi=1000 vs PIHA oskar=1004 — różne uid "pierwszego usera" per host. - -**Kierunek naprawy (do rozważenia, osobny projekt):** -- Ustalić KANONICZNE uid/gid per rola: agent=1000 wszędzie; dedykowane grupy dla - współdzielonych zasobów (aerbot dla events/rsync-sink na VPS, docker dla socketu). -- Audyt `id ` na KAŻDYM hoście floty (saturn/solaria/piha/vps/lustro) — - zmapować realne uid/gid, udokumentować rozjazdy. -- Rozważyć deklaratywny zapis oczekiwanych uid/gid w hosts/*/host.yaml lub - capabilities.yaml (żeby deploy mógł weryfikować/wymuszać spójność). -- Agenci NIE powinni montować prywatnego .ssh użytkownika — zawsze dedykowany - katalog z własnym kluczem pod właściwym uid (wzorzec z fixa 2026-07-10). - -## Bug: checkpoint observera po ścieżce leksykalnej — kruchy, zatruwa węzeł na zawsze (2026-07-12) - -**Objaw.** PIHA była "martwa" dla observera ~34 dni mimo działającego node-agenta. -Eventy dojeżdżały na VPS (7344 plików w /opt/homelab/events/piha/), ale observer -ich NIE konsumował — `last_seen` nie drgnął, shadow-read logował -`SHADOW_LIVENESS_MISMATCH node=piha event=dead prom=up` z rosnącym wiekiem. - -**Root cause.** `observer_checkpoint.json` trzyma per-węzeł ostatnio przetworzoną -ŚCIEŻKĘ i porównuje ją LEKSYKALNIE (stringowo), awansując tylko "do przodu". -Checkpoint PIHA utknął na `evt-unknown-1781254800-ha_update_available-homeassistant-951.json` -(event z HA, który wpadł do katalogu piha/ z node="unknown"). Nowe eventy nazywają się -`evt-piha--...`, a leksykalnie **"evt-piha-…" < "evt-unknown-…"** (bo `p` < `u`), -więc KAŻDY nowy event był uznawany za starszy niż checkpoint i pomijany. - -**Fix doraźny (zastosowany).** Usunięcie wpisu `piha` z node_checkpoints + restart -observera → 7344 eventy przetworzone, `last_seen_age` spadł z 2 082 036 s (~24 dni) -do 19 s, status=online/fresh, mismatch zniknął. - -**Fix systemowy (ZROBIONE 2026-07-14, `task/fix-observer-checkpoint`).** Checkpoint -per-węzeł trzyma teraz **TIMESTAMP** (int epoch), nie ścieżkę. „Nowy event" = -`ts_z_nazwy_pliku > checkpoint_ts_węzła`; kolejność przetwarzania sortowana po -timestampie, nie leksykalnie. Timestamp parsowany z nazwy `evt---…` -(regex `-(\d{9,11})-`, ten sam co `operator_ui._event_file_ts`); **fallback na -mtime** gdy nazwa nie pasuje — nieparsowalna nazwa NIGDY nie zwraca 0 (0 = leksykalne -„starszy niż checkpoint" = dokładnie ten poison). Migracja starych checkpointów -(ścieżka→ts) przy starcie; nieparsowalna wartość → 0 (reprocess wszystkiego — -bezpieczne, `process_event` jest idempotentne na `last_seen`/`world_state`; lepiej -przetworzyć duplikaty niż zgubić węzeł). Testy regresyjne w -`test_incident_lifecycle.py` (sekcja 9). Znany, akceptowalny warunek brzegowy: -strict `>` może pominąć event o `ts == checkpoint` dostarczony w PÓŹNIEJSZYM cyklu -niż inne eventy z tej samej sekundy — nierealne przy cadence shippingu (rsync co -60 s wysyła całą partię danej sekundy razem; kolejne partie są ~60 s od siebie). - -**Uwaga do idempotencji (zbadane).** Reprocess tego samego eventu NIE psuje -world_state (status/last_seen deterministyczne, resolve incydentu guardowany na -`status=="active"`), ALE `_handle_incident`/`deployment_*` inkrementują -`occurrence_count` i dopisują do `events[]` przy każdym przetworzeniu — reprocess -(np. jednorazowo po migracji) zawyża te liczniki. To kosmetyka, nie korupcja stanu. -Docelowo można dedupować po `event.id` w `events[]` — osobny, drobny task. - -## Bug: ha-diag-agent emituje eventy z node="unknown" do katalogu innego węzła (2026-07-14) — ZROBIONE (2026-07-15, `f2ba81b`) - -**Kontekst.** To był plik-truciciel z buga checkpointu wyżej: -`evt-unknown-1781254800-ha_update_available-homeassistant-951.json` w -`events/piha/`. Node w evencie = `unknown`, ale plik wylądował w katalogu `piha/`. -Sufiks `-951` to `_seq` emittera → agent nachodził długo, wyemitował 951 eventów, -wszystkie jako `node="unknown"`. - -**Root cause (config-wiring).** Tożsamość agenta (`node_name`) i KATALOG eventów -pochodzą z DWÓCH niezależnych źródeł: -- `services/ha-diag-agent/src/ha_diag/config.py:20` → `node_name: str = "unknown"` - (domyślne, gdy env `NODE_NAME` nie dotrze do procesu w kontenerze). -- `services/ha-diag-agent/docker-compose.yml:12` → wolumen - `/opt/homelab/events/${NODE_NAME:-ha-diag}:/events` — `${NODE_NAME}` jest - interpolowane po stronie HOSTA (compose), a katalog jest dodatkowo twardo - przypięty do `piha` w `hosts/piha/runtime/ha-diag-agent/docker-compose.override.yml`. - -Jeśli `NODE_NAME` trafi do interpolacji wolumenu/override (→ `piha`), ale NIE do -`environment:` procesu (albo `Settings.load()` przez `os.environ.setdefault` go nie -nadpisze), aplikacja czyta `node_name="unknown"` i pisze eventy `node="unknown"` -do katalogu `events/piha/`. Rozjazd między nazwą w evencie a katalogiem docelowym. - -**Skutek.** Poza zatruciem checkpointu (już naprawione osobno): eventy `node="unknown"` -są bezużyteczne dla world_state (observer tworzy węzeł-widmo `unknown`, potem prune go -kasuje bo nie ma go w topologii) — realny sygnał z ha-diag na piha przepada. - -**Fix — ZROBIONE (2026-07-15, `f2ba81b`, `docs/sessions/2026-07-15.md`).** Wariant (a): -`node_name` NIGDY nie może być `"unknown"` w produkcji. `config.py` -`Field(default="unknown", validate_default=True)` + validator odrzuca `""`/`"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 (rebuild, `NODE_NAME=piha` dochodzi do procesu). Pliki -`evt-unknown-*` na VPS/PIHA: 0 (potwierdzone). -**Pozostaje osobno (druga warstwa obrony, nadal TODO):** observer/emitter powinien -docelowo odrzucać/kwarantannować event, którego `node` w treści != katalog docelowy — -dzisiejszy fix zamyka źródło (`unknown` nie powstaje), ale nie waliduje spójności -node↔katalog dla innych, przyszłych źródeł eventów. - -## Bug: deploy-local.sh control-plane pada na ghost-kontenerach i zostawia mózg rozłożony (2026-07-12) - -**Objaw.** `bash deploy-local.sh` przerwał się w połowie: -`Error response from daemon: No such container: 5ee6fec5455d_control-plane-ui` → -deploy abort → **ZERO kontenerów control-plane** (`docker ps -a` pusty). Mózg całkowicie -down. Zdarzyło się DWA RAZY tego samego dnia. Ratunek: ponowny `deploy-local.sh` -(gdy ghost już zniknął, compose stawia czysto). - -**Root cause (podejrzenie).** Ten sam COMPOSE_PROJECT_NAME divergence / ghost -hash-prefixed containers, co przy `deploy.sh vps` (naprawione guardem 3b71707). -Compose próbuje Recreate kontenera pod nazwą `_control-plane-ui`, której Docker -już nie zna → błąd → `set -e` przerywa deploy w połowie. - -**Fix (DO ZROBIENIA).** deploy-local.sh powinien być odporny: cleanup ghostów przed -recreate (`docker compose down --remove-orphans` albo jawne usunięcie hash-prefixed -kontenerów), ewentualnie jawny `-p` (COMPOSE_PROJECT_NAME) żeby nazwy były deterministyczne. -Deploy mózgu NIE MOŻE zostawiać control-plane w stanie zero-kontenerów. - -## Fix: paperless-worker@SOLARIA — dwa bugi configu, naprawione i zweryfikowane na żywo (2026-07-12) - -**Kontekst.** Moduł 3 (`services/paperless-worker/`, split-host OCR worker) był -zdeployowany i brał zadania z kolejki, ale miał dwa bugi w compose: - -1. **`command: celery ...` nie odpalał celery.** Obraz paperless-ngx - (`/sbin/docker-entrypoint.sh`) routuje każdy argument NIE zaczynający się - od `/` do `manage.py` — więc `celery` lądował jako nieznana subkomenda - Django, nie jako program. Fix: `command: /usr/sbin/gosu paperless - /usr/local/bin/celery ...` (ścieżka absolutna wchodzi w gałąź `exec "$@"` - entrypointu; `gosu paperless` z przodu bo ta gałąź nie dostaje automatycznego - gosu, inaczej proces poszedłby jako root i zepsuł właściciela plików na NFS). -2. **Brak współdzielonego `SCRATCH_DIR`.** Paperless@PIHA staguje wgrywany - plik w `/tmp/paperless` (domyślny `SCRATCH_DIR`) i niesie tę ścieżkę w - payloadzie zadania celery jako ścieżkę absolutną. Worker@SOLARIA miał - własny, lokalny `/tmp/paperless` — gdy odbierał zadanie zamiast workera - PIHA, padał `Cannot consume ...: File not found`. Fix: NFS volume - `paperless_scratch` (ten sam wzorzec co `data/media/consume`) na - `/opt/homelab/data/paperless/scratch` (już istniał na PIHA, `chown 1000:1000`), - mount na `/tmp/paperless` po obu stronach. - -Oba fixy + uzasadnienie: `services/paperless/docker-compose.yml`, -`services/paperless-worker/docker-compose.yml`, `services/paperless-worker/README.md`. -Zweryfikowane end-to-end na żywo (branch `task/paperless-worker-fix`, jeszcze -niezmergowany do master w momencie pisania tego wpisu): 3 dokumenty testowe -wrzucone do `consume/` na PIHA, jeden odebrany i dokończony przez worker@SOLARIA -(log: `ocrmypdf`/`tesseract` → `ConsumeTaskPlugin completed with: Success`), -zero `File not found`. Dokumenty testowe usunięte po teście (`document.delete()` -+ ręczny cleanup plików) — produkcyjne 6 dokumentów nietknięte. - -**Otwarte (świadomie odłożone, nie blokuje działania):** -- `hosts/solaria/services.yaml` i `inventory/topology.yaml` nie mają wpisu - `paperless-worker` (SOLARIA ma tam tylko `node-agent`) — było zaplanowane w - cutover checkliście README jako krok "przy deployu", ale nigdy nie zrobione. - Bez tego wpisu supervisor/observer nie widzą tego serwisu w desired-state — - drift (np. worker padnie i nie wstanie) nie zostanie automatycznie wykryty - przez agent system, tylko przez brak przetwarzania kolejki. -- Test formalnego fallbacku (stop worker@SOLARIA → kolejka mieli na PIHA → - start → drenaż) nie był wykonany w tej sesji — mechanizm nie zmienił się - tym fixem (był już OK), ale warto zweryfikować przy okazji. - -## Nowe serwisy KB nie sa w monitoringu (desired-state) - -**Data:** 2026-07-12 - -**Problem:** Zdeployowane serwisy filaru dokumentow nie maja wpisow w `hosts/*/services.yaml` -i `inventory/topology.yaml`, wiec supervisor/observer ich NIE WIDZA w desired-state: -- `paperless` + `paperless-db` + `paperless-broker` (PIHA) — Deploy 1, 2026-07-10 -- `paperless-worker` (SOLARIA) — Deploy 2, 2026-07-12 (SOLARIA ma tam tylko `node-agent`) - -**Skutek:** drift nie jest wykrywany. Jesli worker padnie i nie wstanie, albo paperless -przestanie dzialac — agent system tego nie zglosi. Dowiesz sie dopiero po tym, ze kolejka -nie jest przetwarzana / strona nie odpowiada. - -**Fix:** dodac wpisy do `hosts/piha/services.yaml`, `hosts/solaria/services.yaml`, -`inventory/topology.yaml`. Zweryfikowac ze observer/supervisor je widza (healthcheck, -liveness). Dotyczy tez przyszlych: nextcloud, gokapi. - -**Zasada na przyszlosc:** rejestracja w services.yaml/topology to CZESC deployu, nie osobny -krok "kiedys" — inaczej kazdy nowy serwis to slepy punkt monitoringu. - -## Bug: deploy-node.sh nie przebudowuje obrazu — deploy "OK" ale nowy kod nie wchodzi (2026-07-15) — ✅ ZROBIONE (2026-07-16, commit `77defff`) - -**Objaw.** `deploy-node.sh ` robi `docker compose up -d` BEZ `--build`. Dla -serwisów z Dockerfile (ha-diag-agent, node-agent, llm-gateway, brain-watchdog, itd.), -gdy zmienia się TYLKO kod (nie compose/env), compose widzi "kontener działa, obraz ten -sam" → status `Running` (0.0s), NIE przebudowuje i NIE recreatuje. Nowy kod z repo NIE -wchodzi w życie mimo `git pull` i "Deployment Complete". - -**Skutek — cicha rozbieżność repo↔runtime.** Deploy raportuje sukces, a kontener biega -na starym obrazie. Ugryzło DWA razy: fleet-prometheus (config nie wchodził bez -force-recreate) i ha-diag-agent 2026-07-15 (fix node_name był w repo `f2ba81b`, ale -`Running` zamiast rebuild — trzeba było ręcznego `docker compose up -d --build ---force-recreate`). - -**Root cause.** deploy-node.sh (~linia 110) w pętli deployu: brak `--build` w wywołaniu -compose. Docker cache'uje obraz po tagu, nie po zawartości src/. -compose. Docker cache'uje obraz po tagu, nie po zawartości src/. - -**Fix — ZROBIONE (2026-07-16, `77defff`).** deploy-node.sh wywołuje teraz `--build` -warunkowo, gdy serwis ma top-level `Dockerfile` (`test -f services//Dockerfile`); -prebuilt serwisy bez `--build` (no-op). Zweryfikowane w boju na PIHA: 6 serwisów -(node-agent/ha-diag/brain-watchdog/llm-gateway → Building; vikunja/kb-postgres → prebuilt). -**Follow-up pozostawiony**: `agent-system` ma build w podkatalogach bez top-level -Dockerfile — niezarejestrowany przez tę detekcję, osobny task. - -## Ollama SOLARIA: brak sterownika NVIDII — ZAMKNIĘTE (2026-07-16) - -**Kontekst.** Cutover 2026-07-15 (`docs/infra/ollama-solaria-cutover-2026-07-15.md`) -odkrył, że SOLARIA nie miała zainstalowanego żadnego sterownika NVIDII — -`nvidia-smi` nie istniał na hoście. `hosts/solaria/services.yaml` opisywał -ollama jako "GPU-backed" od dawna, ale to było aspiracyjne — Ollama zawsze -szła CPU-only. GPU reservation zakomentowana w -`services/ollama/docker-compose.yml` (`f57a01a`); item trafił do backlogu -jako blokujący fazę mailową embeddingów (moduł 5). - -**Fix (2026-07-16).** Zainstalowany `nvidia-driver-595-open` z repo dystrybucji -(nie stary PPA `graphics-drivers` dla jammy — zdezaktywowany przez rename na -`.disabled`). RTX 4070 Ti SUPER 16GB, CUDA 13.2, `nvidia-smi` działa na -hoście. `nvidia-container-toolkit` był już obecny (doinstalowany jako -prerequisite przy cutoverze 07-15). GPU reservation przywrócona w compose. -Pomiar throughput GPU vs CPU baseline (0.79s/chunk) — patrz -`jobs/documents-ingest/README.md`, sekcja timing. - -**Status:** ZAMKNIĘTE. - -**Follow-upy pozostawione (osobne taski):** -- **Batching wywołań Ollamy** — przed fazą mailową (225k kopert). Sekwencyjne - wywołania `/api/embeddings` (nawet na GPU) będą wąskim gardłem przy takiej - skali; ocenić równoległość/batch API Ollamy. -- **`UNIQUE(envelope_id, chunk_index)` bez `model`** w `document_chunk` - (`services/kb-postgres/init/002_chunks.sql`) — re-embedding innym modelem - cicho no-opuje się przez istniejący constraint. Schema change do zrobienia - przy fazie 3 (patrz `jobs/documents-ingest/README.md`, sekcja "Idempotency" - kroku 6 embed). - -## Follow-upy z etapu 0 (truth cleanup, 2026-07-29) - -**Źródło**: sesja merge `task/etap0-truth` (topologia: status active|dormant, -listy serwisów usunięte z topology.yaml — node-level truth only). - -- **40-register.sh emituje stary schemat topologii** — szablon bloku node'a - w `scripts/onboard/steps/40-register.sh` nadal zawiera listę `services:` - i nie ma pola `status:`; wyrównać z nowym schematem node-level-only - (`inventory/topology.yaml`). -- **Historyczny komentarz mqtt_unreachable w observerze** — `scripts/observer/ - observer.py:866` wspomina routing `mqtt_unreachable -> container_restart` - usunięty z supervisora (recon D15); sprzątnąć przy najbliższej edycji pliku. diff --git a/docs/sessions/2026-07-31-kb-f4-final-narty27.md b/docs/sessions/2026-07-31-kb-f4-final-narty27.md index 13b48f0..96ac9e6 100644 --- a/docs/sessions/2026-07-31-kb-f4-final-narty27.md +++ b/docs/sessions/2026-07-31-kb-f4-final-narty27.md @@ -1,3 +1,12 @@ +--- +okf: "0.1" +type: session-log +visibility: private +status: active +updated: 2026-08-04 +links: [] +--- + # Session log 2026-07-31 — KB Faza 4: zamknięcie + pilot narty27 ## Zakres diff --git a/kb/decisions/backlog-aktywne.md b/kb/decisions/backlog-aktywne.md new file mode 100644 index 0000000..d471ebd --- /dev/null +++ b/kb/decisions/backlog-aktywne.md @@ -0,0 +1,625 @@ +--- +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 `services/narty27/README.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//config/`) — +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 --delete ` (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//config/` 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**: `services/home-assistant/docs/audyt-automatyzacji-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**: `services/home-assistant/docs/audyt-automatyzacji-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**: `services/home-assistant/docs/audyt-automatyzacji-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**: `services/home-assistant/docs/audyt-automatyzacji-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//` 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 +`docs/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 `: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//.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/.conf się NIE generuje → "unrecognized name" mimo + dobrego certu. Objaw mylący (wygląda jak problem certu/DNS). + Diagnoza: `strings /data/database.sqlite | grep ` → 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 `). 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. + +--- + diff --git a/kb/decisions/backlog-anomaly-detection-liveness.md b/kb/decisions/backlog-anomaly-detection-liveness.md new file mode 100644 index 0000000..ecbe0ea --- /dev/null +++ b/kb/decisions/backlog-anomaly-detection-liveness.md @@ -0,0 +1,29 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Anomaly detection liveness — mózg uczy się wzorca dobowego per node (pomysł 2026-06-26) + +**Idea**: zamiast statycznych okien czasowych w regułach alertowych (np. "lustro 7-23"), +mózg (supervisor/observer) czyta historię metryk z Prometheus (range queries / Grafana) +i SAM wykrywa wzorzec dobowy każdego węzła. `up==0` zgodne z nauczonym wzorcem offline +(lustro zwykle off nocą, solaria nieregularnie) = NIE anomalia, nie alarmuj. `up==0` +odbiegające od wzorca = realna awaria → alert. Inteligencja w mózgu + dane jako źródło +wzorca, nie sztywne godziny wpisywane ręcznie. + +**Warunek**: wymaga TYGODNI historii metryk. fleet-prometheus postawiony 2026-06-25 → +realne dopiero za ~2-4 tygodnie, gdy uzbiera się wzorzec dobowy. + +**Pułapka**: uczący się system może przeoczyć realną awarię pokrywającą się z typowym +oknem offline (statyczna reguła jest głupia, ale przewidywalna). Uwzględnić przy projektowaniu. + +**Na teraz**: targety scrape'owane BEZ polityki alertowej, label tylko `node:`. Prometheus +gromadzi historię. Anomaly detection = osobny świadomy projekt później (CC, z testami). + +--- diff --git a/kb/decisions/backlog-deploy-host-pusty.md b/kb/decisions/backlog-deploy-host-pusty.md new file mode 100644 index 0000000..67d9227 --- /dev/null +++ b/kb/decisions/backlog-deploy-host-pusty.md @@ -0,0 +1,19 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## `scripts/deploy/deploy-host.sh` to pusty plik (2026-08-03) + +**Data**: 2026-08-03 +**Źródło**: sesja `task/redeploy-fix`. +**Problem**: 0-bajtowy, wykonywalny stub w `scripts/deploy/` — wygląda jak +entrypoint, nie robi nic. Kandydat do skasowania (jak `deploy-role.sh` w etapie 0). + +--- + diff --git a/kb/decisions/backlog-deploy-runner-instalacja.md b/kb/decisions/backlog-deploy-runner-instalacja.md new file mode 100644 index 0000000..e735d38 --- /dev/null +++ b/kb/decisions/backlog-deploy-runner-instalacja.md @@ -0,0 +1,41 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## deploy-runner: instalacja na węzłach + E2E redeployu (2026-08-03) + +**Data**: 2026-08-03 +**Źródło**: sesja `task/redeploy-fix` — recon D14/D15 + OPEN QUESTION 4 (nieopróżniona +kolejka akcji). +**Było**: `redeploy` nie mógł się wykonać nigdy. Executor odpalał +`scripts/deploy/deploy-node.sh ` **wewnątrz swojego kontenera** — +skrypt ignoruje oba argumenty i wymaga repo w `${HOME}/homelab-codex-ws` +(w kontenerze `HOME=/home/homelab`, katalog nie istnieje) → `exit 1` w 18. linii. +Za tym stały jeszcze trzy blokady: brak `git`, brak klienta `docker` w obrazie oraz +— gdyby przeszedł — deploy **całego zestawu usług hosta executora**, nie węzła +z akcji. Stąd `healthcheck_failed` przekierowany 2026-07-29 na `container_restart` +i 18 pending / 0 completed. +**Zrobione w repo (ta sesja)**: `scripts/deploy/deploy-service.sh` (deploy jednej +usługi, wspólny z `deploy-node.sh` — ta sama inwokacja compose, więc ta sama nazwa +projektu), `jobs/deploy-runner/` (systemd na hoście: rsync-pull akcji, walidacja, +deploy, `action_result` z powrotem), executor dispatchuje `redeploy` do +`actions/deploy//` i rozlicza je jak `container_restart` +(`REDEPLOY_TIMEOUT_SECS=900`). 248 testów zielonych. +**Do zrobienia (runtime, wymaga operatora)**: +1. Deploy control-plane na VPS (nowy executor + `../..:/repo:ro`). +2. Instalacja `jobs/deploy-runner/` na vps, piha, solaria — patrz README + („Install (per node)"). Na VPS `VPS_EVENTS_HOST` musi zostać puste. +3. E2E na benignej usłudze na PIHA (wzorzec `test-e2e-b` z 2026-07-23), potem + opróżnienie kolejki 18 pending — w tym `redeploy-vps-gokapi`. +4. SOLARIA: `group_add: "996"` dla node-agenta wciąż niewdrożony (recon 641-649) — + dopóki nie wejdzie, `container_restart` tam nie działa (redeploy działa, bo nie + idzie przez node-agenta). + +--- + diff --git a/kb/decisions/backlog-disk-cleanup-zepsuty.md b/kb/decisions/backlog-disk-cleanup-zepsuty.md new file mode 100644 index 0000000..e12584e --- /dev/null +++ b/kb/decisions/backlog-disk-cleanup-zepsuty.md @@ -0,0 +1,24 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## disk_cleanup w executorze jest zepsuty tak samo jak był redeploy (2026-08-03) + +**Data**: 2026-08-03 +**Źródło**: sesja `task/redeploy-fix` (znalezione przy okazji, poza zakresem zadania). +**Problem**: `executor._execute_disk_cleanup()` odpala `ssh oskar@ …`, a obraz +control-plane (`python:3.11-slim` + `pip install pyyaml`) **nie ma klienta ssh** — +`subprocess.run(["ssh", …])` leci `FileNotFoundError`, akcja ląduje w `failed/`. +To ta sama klasa błędu co redeploy i sprzeczne z decyzją „bez SSH w executorze". +**Fix**: przenieść `disk_cleanup` na model dispatch (node-agent albo deploy-runner — +runner ma już hosta i uprawnienia) albo usunąć typ akcji. Do decyzji przy okazji +opróżniania kolejki. + +--- + diff --git a/kb/decisions/backlog-followupy-etap0.md b/kb/decisions/backlog-followupy-etap0.md new file mode 100644 index 0000000..b39b21b --- /dev/null +++ b/kb/decisions/backlog-followupy-etap0.md @@ -0,0 +1,22 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Follow-upy z etapu 0 (truth cleanup, 2026-07-29) + +**Źródło**: sesja merge `task/etap0-truth` (topologia: status active|dormant, +listy serwisów usunięte z topology.yaml — node-level truth only). + +- **40-register.sh emituje stary schemat topologii** — szablon bloku node'a + w `scripts/onboard/steps/40-register.sh` nadal zawiera listę `services:` + i nie ma pola `status:`; wyrównać z nowym schematem node-level-only + (`inventory/topology.yaml`). +- **Historyczny komentarz mqtt_unreachable w observerze** — `scripts/observer/ + observer.py:866` wspomina routing `mqtt_unreachable -> container_restart` + usunięty z supervisora (recon D15); sprzątnąć przy najbliższej edycji pliku. diff --git a/kb/decisions/backlog-kb-serwisy-poza-monitoringiem.md b/kb/decisions/backlog-kb-serwisy-poza-monitoringiem.md new file mode 100644 index 0000000..e8d28d7 --- /dev/null +++ b/kb/decisions/backlog-kb-serwisy-poza-monitoringiem.md @@ -0,0 +1,30 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Nowe serwisy KB nie sa w monitoringu (desired-state) + +**Data:** 2026-07-12 + +**Problem:** Zdeployowane serwisy filaru dokumentow nie maja wpisow w `hosts/*/services.yaml` +i `inventory/topology.yaml`, wiec supervisor/observer ich NIE WIDZA w desired-state: +- `paperless` + `paperless-db` + `paperless-broker` (PIHA) — Deploy 1, 2026-07-10 +- `paperless-worker` (SOLARIA) — Deploy 2, 2026-07-12 (SOLARIA ma tam tylko `node-agent`) + +**Skutek:** drift nie jest wykrywany. Jesli worker padnie i nie wstanie, albo paperless +przestanie dzialac — agent system tego nie zglosi. Dowiesz sie dopiero po tym, ze kolejka +nie jest przetwarzana / strona nie odpowiada. + +**Fix:** dodac wpisy do `hosts/piha/services.yaml`, `hosts/solaria/services.yaml`, +`inventory/topology.yaml`. Zweryfikowac ze observer/supervisor je widza (healthcheck, +liveness). Dotyczy tez przyszlych: nextcloud, gokapi. + +**Zasada na przyszlosc:** rejestracja w services.yaml/topology to CZESC deployu, nie osobny +krok "kiedys" — inaczej kazdy nowy serwis to slepy punkt monitoringu. + diff --git a/kb/decisions/backlog-m1-solaria-prune-mitigation.md b/kb/decisions/backlog-m1-solaria-prune-mitigation.md new file mode 100644 index 0000000..9749fd9 --- /dev/null +++ b/kb/decisions/backlog-m1-solaria-prune-mitigation.md @@ -0,0 +1,31 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-04 +links: + - ../phases/backlog.md + - ../incidents/2026-07-30-ollama-solaria-vanish.md +--- + +## M1 aktywna na SOLARII — cleanup node-agenta wyłączony do czasu R1 (2026-08-04) + +**Data**: 2026-08-04 +**Źródło**: `docs/incidents/2026-07-30-ollama-solaria-vanish.md` (§7, M1). +**Stan**: w `hosts/solaria/runtime/node-agent/docker-compose.override.yml` ustawiono +`NODE_TYPE=lte_node` (było `ai_node`) na czas backfillu embed (faza mailowa KB). +`lte_node` powoduje wczesny return w `run_safe_cleanup()`, więc na SOLARII nie działa +niefiltrowany `docker container prune` kasujący zatrzymane kontenery w ≤60 s — +łącznie z tymi, które mają `restart: unless-stopped` i zostały zatrzymane świadomie. +`self.node_type` jest czytane wyłącznie w `run_safe_cleanup()` i dwóch liniach logu, +więc monitoring, eventy i dispatch akcji działają bez zmian. +**Skutek uboczny**: `lte_node` pomija CAŁY cleanup — na czas mitygacji nie są sprzątane +dangling images ani build cache. Pilnować miejsca na dysku SOLARII. +**Nie zdeployowane** — zmiana jest tylko w repo (branch `task/m1-prune-mitigation`); +wchodzi przy najbliższym redeployu node-agenta na SOLARII. +**Zdjąć po**: wdrożeniu R1 (filtrowanie prune po restart policy / labelu compose) na tym +nodzie — wtedy przywrócić `NODE_TYPE=ai_node`. R1–R3 w toku po stronie subsystemu A. + +--- + diff --git a/kb/decisions/backlog-rozjazdy-repo-rzeczywistosc.md b/kb/decisions/backlog-rozjazdy-repo-rzeczywistosc.md new file mode 100644 index 0000000..d29ba71 --- /dev/null +++ b/kb/decisions/backlog-rozjazdy-repo-rzeczywistosc.md @@ -0,0 +1,83 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Rozjazdy repo<->rzeczywistosc (z inwentaryzacji 2026-06-30) +**Zrodlo**: `docs/infra/inventory-2026-06-30.md` (23 rozjazdy, pelna tabela tam). +**Weryfikacja 2026-07-02**: `docs/infra/inventory-verify-2026-07-02.md` — bilans: +20 wciaz aktualnych, 2 zmienione, 1 wyjasniony (storage SOLARIA = partycja Windows +dual-boot, NIE rozjazd — zdjety z listy). +Ponizej te wymagajace akcji, pogrupowane wg ryzyka. Naprawa = osobny task/kilka. + +### Grupa A — czyste docs, zero ryzyka +- ✅ ZROBIONE (2026-07-02, commit `886bc85`) — **forgejo** `service.yaml owner_node`: + saturn -> piha (biega na PIHA always-on) +- ✅ ZROBIONE (2026-07-02, commit `886bc85`) — **mosquitto** `service.yaml owner_node`: + piha -> vps (biega na VPS, nie na PIHA) +- **capabilities SATURN**: RAM 8 -> 14GiB; dysk sd-card 64GB -> /dev/sda 159GB +- **capabilities SOLARIA**: CPU 24 -> 32 nproc +- **`hosts/saturn/services.yaml`** nie istnieje — 5 kontenerow bez deklaracji +- **`hosts/vps/services.yaml`** niekompletne (4 z 9 z topology); **solaria** tez (brak planner-agent) + +### Grupa B — wymaga decyzji +- **npm x2**: PIHA (LAN ingress :80/:443) + VPS (public). Repo zna jedna (owner=vps). + Decyzja: zostawic oba (intentional, wildcard cert via NPM@PIHA) czy usunac PIHA? + Jesli oba zamierzone -> dodac piha do service.yaml + hosts/piha. +- ✅ ZROBIONE (2026-07-02) — **control-plane na SATURN**: `docker compose down` + (wolumeny zachowane). Supervisor byl SLEPY (brak mountu repo, WARNING loop + "Hosts directory /repo/hosts does not exist" co 30s) — zero ryzyka zdublowanych + remediacji przez te 3 dni. Jedyny control-plane = produkcyjny na VPS. +- **ollama**: `service.yaml owner=solaria` ale NIE biega. Wdrozyc czy wyrzucic z repo? + +### Grupa C — sprzatanie +- **control-plane-ui healthcheck**: uzywa `curl` ktorego NIE MA w obrazie -> failuje w + kolko -> UNHEALTHY + log spam (4.2G syslog na SATURN). Fix: wget/nc w healthcheck + albo curl w Dockerfile. (Przyczyna rozjazdu #6 znaleziona przy gaszeniu dysku.) +- **homeassistant5 na PIHA** (HA "ken" :8123) niedeklarowany -> dodac do hosts/piha + topology +- **VPS**: outline-postgres-1 anonimowy image (4e6e670bb069) -> named tag; + humanai-landing/mailer/umami do repo. ~~joplin-db postgres:18 -> 17/16~~ + (ocena zdezaktualizowana 2026-07-02: PG18 GA od 09/2025, nie pre-release — bez akcji) +- **PIHA: 33 shadow kontenery** poza GitOps (immich, vaultwarden, wikijs, actual, + audiobookshelf, elasticsearch, grafana, prom, portainer, code-server, diskover...) + -> audyt + stopniowo do hosts/piha/services.yaml +- **zigbee2mqtt** topology mowi chelsty-infra, biega na PIHA -> poprawic topology +- **stability-agent / node_exporter** owner_node single, biegaja wielomiejscowo -> per-host + +### Followupy z weryfikacji + rozbrajania min (2026-07-02) +**Zrodlo**: `docs/infra/inventory-verify-2026-07-02.md` + sesja 2026-07-02. +Zgloszone przy fixie owner_node (`886bc85`), swiadomie NIE ruszone — osobne decyzje. + +- **forgejo** brak wpisu w `hosts/piha/services.yaml`; **mosquitto** brak + w `hosts/vps/services.yaml` — schemat hostowy wymaga role/exposure/depends_on + (miny #2/#3/#16 z audytu). +- **mosquitto na VPS bez mem_limit override** w `hosts/vps/runtime/` — + narusza konwencje CLAUDE.md (kazdy serwis VPS deklaruje mem_limit). +- **drugi mosquitto na chelsty-infra** (offline'owa instancja) — pojedyncze + `owner_node` jej nie opisuje; wzorzec per-host jak stability-agent / + node_exporter (miny #17/#18). +- **topology.yaml:75**: mosquitto zadeklarowany tez jako komponent ai-cluster — + rozstrzygnac, czyj jest broker :1883. +- **pi-watchtower-1 na LUSTRO w restart-loopie** (nowe z reconu; node-agent healthy). +- **alias `lustro` nie rezolwuje z SOLARII** (nowe z reconu). +- **fleet-prometheus bez formalnego override mem_limit** w `hosts/vps/runtime/` — + limit siedzi w bazowym compose (kosmetyka). + +### Po odchudzaniu PIHA (2026-07-02, faza 2 modulu 0) +- **llm-gateway: zlokalizowac/zarchiwizowac zrodlo** — kod (wlasny FastAPI router -> + Ollama@SOLARIA) moze zyc TYLKO w `/opt/llm-gateway` na PIHA, bez gita; przeszukanie + PIHA i repo nie znalazlo innej kopii. Zarchiwizowac do repo/Forgejo zanim padnie nosnik. +- **Prometheus@PIHA: target llm-gateway blednie nazwany `watchtower`** — celuje w :8080 + i odpytuje `/v1/metrics`, dostaje wieczne 404 (llm-gateway nie serwuje metryk). + Naprawic nazwe/endpoint albo usunac target. + +### Tech debt SATURN (z gaszenia dysku 2026-06-30) +- **`/opt/anaconda3` 16G** — najwiekszy pojedynczy zjadacz dysku (env-y Pythona). Decyzja Oskara kiedy/czy czyscic. +- Dysk 91% -> 83% ugaszone (docker prune + journal + syslog), ale `/home` zaszyfrowany + i ciasny strukturalnie. SATURN dzwiga dev + drugi control-plane + agent-webui — napiecie. + diff --git a/kb/decisions/backlog-uid-gid-flota.md b/kb/decisions/backlog-uid-gid-flota.md new file mode 100644 index 0000000..ef83f99 --- /dev/null +++ b/kb/decisions/backlog-uid-gid-flota.md @@ -0,0 +1,43 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Tech-debt: globalny porządek uid/gid/uprawnień we flocie (2026-07-10) + +**Diagnoza.** Flota NIE ma spójnej mapy uid/gid. "oskar" ma różne uid per host +(PIHA: 1004, inne hosty: prawdopodobnie 1000/inne). Kontenery agentów zakładają +uid 1000 (user "homelab"). Bind-mounty przenoszą SUROWE uid (nie nazwy) między +hostem a kontenerem → gdy uid hosta ≠ uid zakładany przez kontener, pliki stają +się "cudze" i wybucha cicha awaria (klucz nieczytelny, rsync nie tworzy plików, +socket permission denied). To NIE są przypadki — to systemowy brak kanonicznej +mapy uid/gid. + +**Historia incydentów (dowód że systemowe):** +- 2026-07-10: node-agent PIHA (uid 1000 homelab) montował /home/oskar/.ssh (pliki + uid 1004) → "Load key id_rsa: Permission denied" → rsync padał → 21 dni bez + eventów (wykryte przez shadow-read). Fix: dedykowany /opt/homelab/agent-ssh + chown 1000. +- Wcześniej: oskar spoza grupy `aerbot` na VPS → rsync push nie tworzył plików → + brak cleanup → 8-dniowa cicha awaria floty. Fix: usermod -aG aerbot oskar. +- 2026-07-10 (świeże, PENDING): node-agent PIHA "Docker unavailable: PermissionError(13)" + po recreate — agent nie czyta /var/run/docker.sock (grupa docker/uid). Osobny od + shippingu (nie blokuje eventów), ale ten sam rodzaj problemu — do naprawy + (grupa docker w kontenerze / gid socketu). +- LUSTRO uid pi=1000 vs PIHA oskar=1004 — różne uid "pierwszego usera" per host. + +**Kierunek naprawy (do rozważenia, osobny projekt):** +- Ustalić KANONICZNE uid/gid per rola: agent=1000 wszędzie; dedykowane grupy dla + współdzielonych zasobów (aerbot dla events/rsync-sink na VPS, docker dla socketu). +- Audyt `id ` na KAŻDYM hoście floty (saturn/solaria/piha/vps/lustro) — + zmapować realne uid/gid, udokumentować rozjazdy. +- Rozważyć deklaratywny zapis oczekiwanych uid/gid w hosts/*/host.yaml lub + capabilities.yaml (żeby deploy mógł weryfikować/wymuszać spójność). +- Agenci NIE powinni montować prywatnego .ssh użytkownika — zawsze dedykowany + katalog z własnym kluczem pod właściwym uid (wzorzec z fixa 2026-07-10). + diff --git a/kb/decisions/backlog-zamkniete.md b/kb/decisions/backlog-zamkniete.md new file mode 100644 index 0000000..c8652e3 --- /dev/null +++ b/kb/decisions/backlog-zamkniete.md @@ -0,0 +1,223 @@ +--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Zamknięte + +### Publiczny bind `operator_ui.py:18180` na VPS bez autoryzacji — NAPRAWIONE (2026-07-22, commit `9a5c160`) + +**Data**: 2026-07-22 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Było**: `operator_ui.py` (port 18180) nasłuchiwał na `0.0.0.0` na publicznym VPS +(`135.181.153.108`) bez żadnej autoryzacji. `curl` z zewnątrz do `/actions` zwracał +HTTP 200. `do_POST /action/mutate` przenosi akcje między stanami WŁĄCZNIE z +`"approved"` — dowolna osoba z internetu mogła zatwierdzić akcję remediacyjną. +Jedyne co chroniło do tej pory: executor nie umiał jeszcze wykonać akcji (brak SSH, +patrz wpis niżej) — przypadek, nie zabezpieczenie. +**Naprawione**: dual-bind wg wzorca fleet-prometheus — `127.0.0.1:18180:8080` + +`${TAILSCALE_BIND_IP}:18180:8080`, nowy `services/control-plane/env.example`. +Zweryfikowane po deployu: publiczny IP → HTTP 000, Tailscale → HTTP 200. Konsumenty +(node-agent VPS, materializer PIHA) nietknięte. +**Footgun zapamiętany**: brak `.env` → `docker compose` tylko OSTRZEGA i po cichu +wraca do bindu `0.0.0.0`, nie failuje — patrz wpis „deploy-local.sh: brak twardego +checka .env" w Aktywnych. +**Pozostaje osobno**: `operator_ui.py` nadal bez żadnej autoryzacji (bind zamknięty +chroni przed internetem, nie przed kimkolwiek w mesh Tailscale) — patrz Aktywne. + +--- + +### Remediacja floty bez SSH: executor→node-agent pull przez rsync — ZROBIONE (2026-07-22, commit `2dac154`), E2E potwierdzone 2026-07-23 + +**Data**: 2026-07-22 (implementacja), 2026-07-23 (pierwszy udany cykl E2E) +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Było**: łańcuch supervisor→pending→approved→running działał, ale executor próbował +`ssh oskar@{node} docker restart {container}` — kontener control-plane nie ma klienta +ssh, klucza, ani rozwiązywalnych nazw węzłów. Wykonanie zawsze failowało. +**Decyzja architektoniczna**: bez SSH w executorze (kontener na publicznym VPS z +powłoką na całą flotę = zły blast radius). Kierunek PULL: executor zleca (zapis +`actions/dispatch//.json`), node-agent na docelowym węźle wykonuje +lokalnie przez własny `docker.sock`, wynik wraca eventem `action_result` przez +istniejący rsync-push. VPS nigdy nie inicjuje połączenia do węzła. +**Zabezpieczenia**: walidacja `node == self.node_name`; whitelist typów akcji = +`{container_restart}`; guard przed restartem samego node-agenta; brak wykonywania +dowolnych poleceń z payloadu; idempotencja (ponowne zlecenie = no-op). 183 testy. +**Potwierdzone w boju (2026-07-23)**: cykl `test-e2e-b` — Executing → Dispatched → +Completed w 31 sekund, `node_exporter` na PIHA realnie zrestartowany (uptime 30h→ +minuty), zero połączeń SSH. Pierwszy w historii systemu pełny cykl remediacji. + +--- + +### Uprawnienia `actions/` na PIHA (uid/gid oskar 1004 vs kontener 1000) — NAPRAWIONE ręcznie (2026-07-23, poza repo) + +**Data**: 2026-07-23 +**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`) +**Było**: pierwszy test E2E remediacji bez SSH padał — agent logował `[Errno 13] +Permission denied: /opt/homelab/actions/dispatch` co cykl. `/opt/homelab/actions` +było `oskar:oskar drwxr-xr-x` (utworzone w maju), a działający wzorzec to +`/opt/homelab/events` = `oskar:pi drwxrwsr-x` (grupa `pi`, zapis grupowy, setgid). +Ten sam motyw uid/gid (host oskar 1004 vs kontener 1000) uderzył już czwarty raz — +patrz sekcja „Tech-debt: globalny porządek uid/gid/uprawnień we flocie". +**Naprawione (ręcznie, tylko na PIHA)**: `chgrp -R pi` + `chmod -R g+w` + `chmod g+s` +na `/opt/homelab/actions`. Executor zachował się poprawnie podczas awarii: po 300s +timeoutu przeniósł akcję do `failed` z czytelnym powodem, nic nie zawisło. +**Pozostaje osobno**: fix zastosowany TYLKO na PIHA — SOLARIA i lustro +niezweryfikowane; docelowy fix systemowy to node-agent tworzący +`dispatch//` z właściwymi prawami przy starcie — patrz Aktywne. + +--- + +### Supervisor: pętla zamrożona ~24h, healthy ale nie tika — NAPRAWIONE (2026-07-16, commit `409b583`) + +**Data**: 2026-07-16 +**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane +**Było**: kontener supervisora `healthy`, ale pętla `reconcile()` nie tikała od ~24h, +zero logów. Root cause zweryfikowany na `/proc` (`State:S hrtimer_nanosleep`, NIE +deadlock): `glob` po `EVENTS_DIR` co cykl przy 358k plikach → cykl przekracza brak +timeoutu → nigdy się nie kończy. Logi na DEBUG maskowały objaw. +**Naprawione**: każdy cykl w `ThreadPoolExecutor` z `future.result(timeout=90s, +env SUPERVISOR_RECONCILE_TIMEOUT)`; try/except owija cykl (wyjątek nie zabija pętli); +tick-log co 10 cykli (`SUPERVISOR_TICK_LOG_EVERY`) na INFO; healthcheck sprawdza +świeżość heartbeat, nie tylko czy proces żyje. Zweryfikowane w boju: pętla tika +(cycle #340→#480), cykl #1 timeoutował ale pętla kontynuowała = odporność działa. +**Lekcja**: „healthy kontener ≠ tikająca pętla" — healthcheck musi sprawdzać +świeżość ostatniego cyklu, nie samo czy proces odpowiada. + +--- + +### Event flood 358k plików + retencja martwa od fixu checkpointu — NAPRAWIONE (2026-07-16, commit `dff76ec`) + +**Data**: 2026-07-16 +**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane +**Było**: `EVENTS_DIR` = 358k plików, 91% to `service_healthy` emitowany co cykl per +serwis (stan-jako-zdarzenie, antywzorzec). Retencja `_cleanup_control_plane_fs()` +była martwa od fixu checkpointu `d5139c9` (2026-07-15, patrz „Bug: checkpoint +observera po ścieżce leksykalnej" niżej) — porównanie `str(ścieżka) <= checkpoint_int` +rzucało `TypeError` cicho łapany przez szeroki `except` → backlog rósł bez ograniczenia. +Naprawiając checkpoint wczoraj, złamaliśmy retencję, która na nim polegała. +**Naprawione**: (a) node-agent emituje `service_healthy` tylko na transition +unhealthy→healthy (funkcja dla `observer.process_event` zachowana); (b) retencja +naprawiona epoch-do-epoch; (c) `scripts/maintenance/cleanup_event_backlog.py` +(dry-run + `--apply`). 150 testów pass. Cleanup wykonany na PIHA+VPS: 272 232 pliki +usunięte, backlog 358k→12,7k. **Wynik**: reconcile supervisora przestał timeoutować. +**Lekcja**: migracja typu pola (ścieżka→epoch int) musi audytować WSZYSTKICH +konsumentów tego pola — szeroki `except` maskował dokładnie tę klasę regresji. + +--- + +### Ghost kontenery w panelu (Problem B) — ZNIKNĘŁY (potwierdzone reconem 2026-07-02) + +> ⚠️ ZDEZAKTUALIZOWANE 2026-07-06: ghosty znów widoczne na VPS — patrz wpis +> „Ghosty B WRÓCIŁY" w Aktywnych (`docs/sessions/2026-07-06.md`). + +**Data**: 2026-06-24 (wykryte), 2026-07-02 (zamknięte) +**Źródło**: sesje 2026-06-24/25/26; recon `docs/infra/inventory-verify-2026-07-02.md` +**Było**: martwe kontenery ze starych project-name'ów +(`8547b46c0317_control-plane-supervisor` itp.) raportowane przez observera jako +`error` → `System Status ERROR` w panelu mimo zdrowego mózgu. +**Zamknięte**: recon 2026-07-02 — 24 kontenery na VPS, ZERO hash-prefixed. +Prawdopodobnie recreate'y z kolejnych deployów je zmiotły. Zero akcji ręcznej. +Rozważenie czyszczenia obcych project-name przy deployu — już nieaktualne +(fix A `3b71707` blokuje źródło divergence). + +--- + +### brain-watchdog: poll Prometheus — POTWIERDZONY (recon 2026-07-02) + +**Data**: 2026-06-30 (pending), 2026-07-02 (zamknięte) +**Źródło**: recon `docs/infra/inventory-verify-2026-07-02.md` +**Było**: log startowy nie wypisuje `PROMETHEUS_URL` → brak pewności, że polling +aktywny; diagnoza opierała się na `.env` i braku błędów. +**Zamknięte**: recon potwierdził — obraz zbudowany po `62d6fc0`, `PROMETHEUS_URL` +w `.env` I w env kontenera, zero `poll failed` w logach. Poll aktywny. +Jednolinijkowy log startowy (`polling enabled/disabled`) pozostaje opcjonalną +kosmetyką. ✅ Pełne end-to-end (firing → Telegram) POTWIERDZONE 2026-07-06 +testem `AlertTestEtap0` (Etap 0 cutoveru, `docs/sessions/2026-07-06.md`) — +bez czekania na realną awarię. + +--- + +### Miny #1/#2/#3 z weryfikacji inwentaryzacji — ROZBROJONE (2026-07-02) + +**Źródło**: `docs/infra/inventory-verify-2026-07-02.md`, sesja +`docs/sessions/2026-07-02.md` (tam szczegóły i lekcje). +- **#1 PIHA checkout**: gałąź wciąż `task/kb-gmail-import` po resecie z 2026-06-30 + (reset --hard przesuwa gałąź, nie przełącza) → `checkout master && pull`, + 30 commitów nadrobione. +- **#2 control-plane na SATURN**: supervisor ślepy (brak mountu repo) → + `docker compose down`, wolumeny zachowane. Jedyny mózg = VPS. +- **#3 owner_node**: forgejo→piha, mosquitto→vps (commit `886bc85`). + +--- + +### 🔴 KRYTYCZNY — `deploy.sh vps` niszczył control-plane — NAPRAWIONE (commit `3b71707`) + +**Data**: 2026-06-24 (wykryte), 2026-06-25 (incydent w produkcji), 2026-06-26 (naprawione) +**Źródło**: sesja 2026-06-24 + 2026-06-25 + 2026-06-26 (`docs/sessions/2026-06-26.md`) +**Było (Problem A)**: `control-plane` w `hosts/vps/services.yaml` jako zwykły serwis +pętli `deploy-node.sh`. Pętla używała innego `COMPOSE_PROJECT_NAME` niż `deploy-local.sh` +(cwd=`services/control-plane`). Niezgodność → Recreate → `No such container: +_control-plane-observer` → `set -e` przerywa pętlę → observer/supervisor/executor/ui +znikają. Każdy `deploy.sh vps` rozkładał mózg (potwierdzone w produkcji 2026-06-25). +**Naprawione**: guard w `deploy-node.sh` pomijający serwisy z własnym +`services//deploy-local.sh`. `control-plane` ZOSTAJE w `services.yaml` (gate +pytest+build nadal go testuje), pomijana jest tylko destrukcyjna pętla deployu. +**Potwierdzone w boju**: `deploy.sh vps` wypisał `Skipping control-plane: ma własną +ścieżkę deployu`, mózg `Up 25h healthy`, nietknięty. +**Pozostało osobno**: ghosty z poprzednich rozjazdów (Problem B — patrz Aktywne). + +--- + +### Flaky testy control-plane — state-leak w pytest — NAPRAWIONE (commit `992ff7c`) + +**Data**: 2026-06-24 (zgłoszone), 2026-06-25 (naprawione) +**Źródło**: sesja 2026-06-24 + sesja 2026-06-25 (`docs/sessions/2026-06-25.md`) +**Było**: `test_incident_lifecycle.py` flaky przez state-leak — `OBSERVER_STATE_FILE` +wyprowadzany przy imporcie, helpery patchowały `STATE_DIR` ale nie `OBSERVER_STATE_FILE` → +checkpointy pisane na realny dysk `/opt/homelab/state/` z ścieżkami otagowanymi numerem +przebiegu pytest. Gate deploy.sh czerwony przy zdrowym kodzie. +**Naprawione**: autouse monkeypatch fixture redirectujący WSZYSTKIE ścieżki stanu w tym +`OBSERVER_STATE_FILE`; usunięto buggy `_make_observer`; posprzątano zatruty realny checkpoint. +Weryfikacja: 6/6 przebiegów → 28 passed. Gate rzetelny. + +--- + +### deploy-node.sh — brak `--env-file` (bind 0.0.0.0) — NAPRAWIONE (commit `686aca7`) + +**Data**: 2026-06-25 +**Źródło**: sesja 2026-06-25 (`docs/sessions/2026-06-25.md`) +**Było**: `deploy-node.sh` nie przekazywał `--env-file` do `compose up` → zmienne `.env` +(jak `TAILSCALE_BIND_IP`) interpolowały się do pustego stringa → bind `0.0.0.0` zamiast +Tailscale IP. Potencjalna dziura na publicznym VPS. +**Naprawione**: guard `if [ -f "services//.env" ]` + `--env-file` per-serwis przed +`docker compose up`. Worktree `task/deploy-envfile-fix`, merge ff-only. + +--- + +### Swap 2–4 GB na VPS — ZROBIONE + +**Data**: 2026-06-22 (zgłoszone PENDING w sesji 2026-06-09 flota-recovery) +**Źródło**: sesja 2026-06-22 (`docs/sessions/2026-06-22.md`) +**Było**: VPS (3.7 GB RAM) z `swap=0` → OOM 2026-06-01. +**Zrobione**: `/swapfile` 4 GB aktywny i trwały (wpis w `/etc/fstab`); `vm.swappiness=10` +na żywo i w `/etc/sysctl.conf`. Weryfikacja: `free -h` → `Swap: 4.0Gi (0B used)`, +`/proc/swaps` zawiera `/swapfile`. +**Uwaga**: host-level one-off (nie czysto GitOps), udokumentowany jako celowy host-state +w `hosts/vps/host.yaml`. Brak commitu kodu — zmiana host-side gotowcem. + +--- + +### Observer staleness — martwy node pokazywany NOMINAL + +**Data**: 2026-06-08 (złapane), status: OTWARTY w sensie implementacji +**Problem**: observer/supervisor trzyma ostatni znany stan; brak heartbeat TTL. +Chelsty-infra milczy, ale status NOMINAL podważa zaufanie do panelu. +**Fix**: heartbeat TTL → po przekroczeniu oznacz status `stale` lub `down`. +**Powiązane**: brain-watchdog ślepy na per-node freshness. +*(Otwarty jako TODO implementacyjny — przeniesiony z sesji 2026-06-08)* + diff --git a/kb/incidents/2026-07-12-deploy-local-ghost-kontenery.md b/kb/incidents/2026-07-12-deploy-local-ghost-kontenery.md new file mode 100644 index 0000000..f9fd180 --- /dev/null +++ b/kb/incidents/2026-07-12-deploy-local-ghost-kontenery.md @@ -0,0 +1,28 @@ +--- +okf: "0.1" +type: incident +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Bug: deploy-local.sh control-plane pada na ghost-kontenerach i zostawia mózg rozłożony (2026-07-12) + +**Objaw.** `bash deploy-local.sh` przerwał się w połowie: +`Error response from daemon: No such container: 5ee6fec5455d_control-plane-ui` → +deploy abort → **ZERO kontenerów control-plane** (`docker ps -a` pusty). Mózg całkowicie +down. Zdarzyło się DWA RAZY tego samego dnia. Ratunek: ponowny `deploy-local.sh` +(gdy ghost już zniknął, compose stawia czysto). + +**Root cause (podejrzenie).** Ten sam COMPOSE_PROJECT_NAME divergence / ghost +hash-prefixed containers, co przy `deploy.sh vps` (naprawione guardem 3b71707). +Compose próbuje Recreate kontenera pod nazwą `_control-plane-ui`, której Docker +już nie zna → błąd → `set -e` przerywa deploy w połowie. + +**Fix (DO ZROBIENIA).** deploy-local.sh powinien być odporny: cleanup ghostów przed +recreate (`docker compose down --remove-orphans` albo jawne usunięcie hash-prefixed +kontenerów), ewentualnie jawny `-p` (COMPOSE_PROJECT_NAME) żeby nazwy były deterministyczne. +Deploy mózgu NIE MOŻE zostawiać control-plane w stanie zero-kontenerów. + diff --git a/kb/incidents/2026-07-12-observer-checkpoint-leksykalny.md b/kb/incidents/2026-07-12-observer-checkpoint-leksykalny.md new file mode 100644 index 0000000..925c1fd --- /dev/null +++ b/kb/incidents/2026-07-12-observer-checkpoint-leksykalny.md @@ -0,0 +1,50 @@ +--- +okf: "0.1" +type: incident +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Bug: checkpoint observera po ścieżce leksykalnej — kruchy, zatruwa węzeł na zawsze (2026-07-12) + +**Objaw.** PIHA była "martwa" dla observera ~34 dni mimo działającego node-agenta. +Eventy dojeżdżały na VPS (7344 plików w /opt/homelab/events/piha/), ale observer +ich NIE konsumował — `last_seen` nie drgnął, shadow-read logował +`SHADOW_LIVENESS_MISMATCH node=piha event=dead prom=up` z rosnącym wiekiem. + +**Root cause.** `observer_checkpoint.json` trzyma per-węzeł ostatnio przetworzoną +ŚCIEŻKĘ i porównuje ją LEKSYKALNIE (stringowo), awansując tylko "do przodu". +Checkpoint PIHA utknął na `evt-unknown-1781254800-ha_update_available-homeassistant-951.json` +(event z HA, który wpadł do katalogu piha/ z node="unknown"). Nowe eventy nazywają się +`evt-piha--...`, a leksykalnie **"evt-piha-…" < "evt-unknown-…"** (bo `p` < `u`), +więc KAŻDY nowy event był uznawany za starszy niż checkpoint i pomijany. + +**Fix doraźny (zastosowany).** Usunięcie wpisu `piha` z node_checkpoints + restart +observera → 7344 eventy przetworzone, `last_seen_age` spadł z 2 082 036 s (~24 dni) +do 19 s, status=online/fresh, mismatch zniknął. + +**Fix systemowy (ZROBIONE 2026-07-14, `task/fix-observer-checkpoint`).** Checkpoint +per-węzeł trzyma teraz **TIMESTAMP** (int epoch), nie ścieżkę. „Nowy event" = +`ts_z_nazwy_pliku > checkpoint_ts_węzła`; kolejność przetwarzania sortowana po +timestampie, nie leksykalnie. Timestamp parsowany z nazwy `evt---…` +(regex `-(\d{9,11})-`, ten sam co `operator_ui._event_file_ts`); **fallback na +mtime** gdy nazwa nie pasuje — nieparsowalna nazwa NIGDY nie zwraca 0 (0 = leksykalne +„starszy niż checkpoint" = dokładnie ten poison). Migracja starych checkpointów +(ścieżka→ts) przy starcie; nieparsowalna wartość → 0 (reprocess wszystkiego — +bezpieczne, `process_event` jest idempotentne na `last_seen`/`world_state`; lepiej +przetworzyć duplikaty niż zgubić węzeł). Testy regresyjne w +`test_incident_lifecycle.py` (sekcja 9). Znany, akceptowalny warunek brzegowy: +strict `>` może pominąć event o `ts == checkpoint` dostarczony w PÓŹNIEJSZYM cyklu +niż inne eventy z tej samej sekundy — nierealne przy cadence shippingu (rsync co +60 s wysyła całą partię danej sekundy razem; kolejne partie są ~60 s od siebie). + +**Uwaga do idempotencji (zbadane).** Reprocess tego samego eventu NIE psuje +world_state (status/last_seen deterministyczne, resolve incydentu guardowany na +`status=="active"`), ALE `_handle_incident`/`deployment_*` inkrementują +`occurrence_count` i dopisują do `events[]` przy każdym przetworzeniu — reprocess +(np. jednorazowo po migracji) zawyża te liczniki. To kosmetyka, nie korupcja stanu. +Docelowo można dedupować po `event.id` w `events[]` — osobny, drobny task. + diff --git a/kb/incidents/2026-07-12-paperless-worker-config.md b/kb/incidents/2026-07-12-paperless-worker-config.md new file mode 100644 index 0000000..0c54273 --- /dev/null +++ b/kb/incidents/2026-07-12-paperless-worker-config.md @@ -0,0 +1,51 @@ +--- +okf: "0.1" +type: incident +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Fix: paperless-worker@SOLARIA — dwa bugi configu, naprawione i zweryfikowane na żywo (2026-07-12) + +**Kontekst.** Moduł 3 (`services/paperless-worker/`, split-host OCR worker) był +zdeployowany i brał zadania z kolejki, ale miał dwa bugi w compose: + +1. **`command: celery ...` nie odpalał celery.** Obraz paperless-ngx + (`/sbin/docker-entrypoint.sh`) routuje każdy argument NIE zaczynający się + od `/` do `manage.py` — więc `celery` lądował jako nieznana subkomenda + Django, nie jako program. Fix: `command: /usr/sbin/gosu paperless + /usr/local/bin/celery ...` (ścieżka absolutna wchodzi w gałąź `exec "$@"` + entrypointu; `gosu paperless` z przodu bo ta gałąź nie dostaje automatycznego + gosu, inaczej proces poszedłby jako root i zepsuł właściciela plików na NFS). +2. **Brak współdzielonego `SCRATCH_DIR`.** Paperless@PIHA staguje wgrywany + plik w `/tmp/paperless` (domyślny `SCRATCH_DIR`) i niesie tę ścieżkę w + payloadzie zadania celery jako ścieżkę absolutną. Worker@SOLARIA miał + własny, lokalny `/tmp/paperless` — gdy odbierał zadanie zamiast workera + PIHA, padał `Cannot consume ...: File not found`. Fix: NFS volume + `paperless_scratch` (ten sam wzorzec co `data/media/consume`) na + `/opt/homelab/data/paperless/scratch` (już istniał na PIHA, `chown 1000:1000`), + mount na `/tmp/paperless` po obu stronach. + +Oba fixy + uzasadnienie: `services/paperless/docker-compose.yml`, +`services/paperless-worker/docker-compose.yml`, `services/paperless-worker/README.md`. +Zweryfikowane end-to-end na żywo (branch `task/paperless-worker-fix`, jeszcze +niezmergowany do master w momencie pisania tego wpisu): 3 dokumenty testowe +wrzucone do `consume/` na PIHA, jeden odebrany i dokończony przez worker@SOLARIA +(log: `ocrmypdf`/`tesseract` → `ConsumeTaskPlugin completed with: Success`), +zero `File not found`. Dokumenty testowe usunięte po teście (`document.delete()` ++ ręczny cleanup plików) — produkcyjne 6 dokumentów nietknięte. + +**Otwarte (świadomie odłożone, nie blokuje działania):** +- `hosts/solaria/services.yaml` i `inventory/topology.yaml` nie mają wpisu + `paperless-worker` (SOLARIA ma tam tylko `node-agent`) — było zaplanowane w + cutover checkliście README jako krok "przy deployu", ale nigdy nie zrobione. + Bez tego wpisu supervisor/observer nie widzą tego serwisu w desired-state — + drift (np. worker padnie i nie wstanie) nie zostanie automatycznie wykryty + przez agent system, tylko przez brak przetwarzania kolejki. +- Test formalnego fallbacku (stop worker@SOLARIA → kolejka mieli na PIHA → + start → drenaż) nie był wykonany w tej sesji — mechanizm nie zmienił się + tym fixem (był już OK), ale warto zweryfikować przy okazji. + diff --git a/kb/incidents/2026-07-14-ha-diag-agent-node-unknown.md b/kb/incidents/2026-07-14-ha-diag-agent-node-unknown.md new file mode 100644 index 0000000..f6b10f2 --- /dev/null +++ b/kb/incidents/2026-07-14-ha-diag-agent-node-unknown.md @@ -0,0 +1,48 @@ +--- +okf: "0.1" +type: incident +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Bug: ha-diag-agent emituje eventy z node="unknown" do katalogu innego węzła (2026-07-14) — ZROBIONE (2026-07-15, `f2ba81b`) + +**Kontekst.** To był plik-truciciel z buga checkpointu wyżej: +`evt-unknown-1781254800-ha_update_available-homeassistant-951.json` w +`events/piha/`. Node w evencie = `unknown`, ale plik wylądował w katalogu `piha/`. +Sufiks `-951` to `_seq` emittera → agent nachodził długo, wyemitował 951 eventów, +wszystkie jako `node="unknown"`. + +**Root cause (config-wiring).** Tożsamość agenta (`node_name`) i KATALOG eventów +pochodzą z DWÓCH niezależnych źródeł: +- `services/ha-diag-agent/src/ha_diag/config.py:20` → `node_name: str = "unknown"` + (domyślne, gdy env `NODE_NAME` nie dotrze do procesu w kontenerze). +- `services/ha-diag-agent/docker-compose.yml:12` → wolumen + `/opt/homelab/events/${NODE_NAME:-ha-diag}:/events` — `${NODE_NAME}` jest + interpolowane po stronie HOSTA (compose), a katalog jest dodatkowo twardo + przypięty do `piha` w `hosts/piha/runtime/ha-diag-agent/docker-compose.override.yml`. + +Jeśli `NODE_NAME` trafi do interpolacji wolumenu/override (→ `piha`), ale NIE do +`environment:` procesu (albo `Settings.load()` przez `os.environ.setdefault` go nie +nadpisze), aplikacja czyta `node_name="unknown"` i pisze eventy `node="unknown"` +do katalogu `events/piha/`. Rozjazd między nazwą w evencie a katalogiem docelowym. + +**Skutek.** Poza zatruciem checkpointu (już naprawione osobno): eventy `node="unknown"` +są bezużyteczne dla world_state (observer tworzy węzeł-widmo `unknown`, potem prune go +kasuje bo nie ma go w topologii) — realny sygnał z ha-diag na piha przepada. + +**Fix — ZROBIONE (2026-07-15, `f2ba81b`, `docs/sessions/2026-07-15.md`).** Wariant (a): +`node_name` NIGDY nie może być `"unknown"` w produkcji. `config.py` +`Field(default="unknown", validate_default=True)` + validator odrzuca `""`/`"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 (rebuild, `NODE_NAME=piha` dochodzi do procesu). Pliki +`evt-unknown-*` na VPS/PIHA: 0 (potwierdzone). +**Pozostaje osobno (druga warstwa obrony, nadal TODO):** observer/emitter powinien +docelowo odrzucać/kwarantannować event, którego `node` w treści != katalog docelowy — +dzisiejszy fix zamyka źródło (`unknown` nie powstaje), ale nie waliduje spójności +node↔katalog dla innych, przyszłych źródeł eventów. + diff --git a/kb/incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md b/kb/incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md new file mode 100644 index 0000000..0d45f24 --- /dev/null +++ b/kb/incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md @@ -0,0 +1,35 @@ +--- +okf: "0.1" +type: incident +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Bug: deploy-node.sh nie przebudowuje obrazu — deploy "OK" ale nowy kod nie wchodzi (2026-07-15) — ✅ ZROBIONE (2026-07-16, commit `77defff`) + +**Objaw.** `deploy-node.sh ` robi `docker compose up -d` BEZ `--build`. Dla +serwisów z Dockerfile (ha-diag-agent, node-agent, llm-gateway, brain-watchdog, itd.), +gdy zmienia się TYLKO kod (nie compose/env), compose widzi "kontener działa, obraz ten +sam" → status `Running` (0.0s), NIE przebudowuje i NIE recreatuje. Nowy kod z repo NIE +wchodzi w życie mimo `git pull` i "Deployment Complete". + +**Skutek — cicha rozbieżność repo↔runtime.** Deploy raportuje sukces, a kontener biega +na starym obrazie. Ugryzło DWA razy: fleet-prometheus (config nie wchodził bez +force-recreate) i ha-diag-agent 2026-07-15 (fix node_name był w repo `f2ba81b`, ale +`Running` zamiast rebuild — trzeba było ręcznego `docker compose up -d --build +--force-recreate`). + +**Root cause.** deploy-node.sh (~linia 110) w pętli deployu: brak `--build` w wywołaniu +compose. Docker cache'uje obraz po tagu, nie po zawartości src/. +compose. Docker cache'uje obraz po tagu, nie po zawartości src/. + +**Fix — ZROBIONE (2026-07-16, `77defff`).** deploy-node.sh wywołuje teraz `--build` +warunkowo, gdy serwis ma top-level `Dockerfile` (`test -f services//Dockerfile`); +prebuilt serwisy bez `--build` (no-op). Zweryfikowane w boju na PIHA: 6 serwisów +(node-agent/ha-diag/brain-watchdog/llm-gateway → Building; vikunja/kb-postgres → prebuilt). +**Follow-up pozostawiony**: `agent-system` ma build w podkatalogach bez top-level +Dockerfile — niezarejestrowany przez tę detekcję, osobny task. + diff --git a/kb/incidents/2026-07-16-ollama-solaria-brak-sterownika.md b/kb/incidents/2026-07-16-ollama-solaria-brak-sterownika.md new file mode 100644 index 0000000..cd47a9f --- /dev/null +++ b/kb/incidents/2026-07-16-ollama-solaria-brak-sterownika.md @@ -0,0 +1,40 @@ +--- +okf: "0.1" +type: incident +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Ollama SOLARIA: brak sterownika NVIDII — ZAMKNIĘTE (2026-07-16) + +**Kontekst.** Cutover 2026-07-15 (`docs/infra/ollama-solaria-cutover-2026-07-15.md`) +odkrył, że SOLARIA nie miała zainstalowanego żadnego sterownika NVIDII — +`nvidia-smi` nie istniał na hoście. `hosts/solaria/services.yaml` opisywał +ollama jako "GPU-backed" od dawna, ale to było aspiracyjne — Ollama zawsze +szła CPU-only. GPU reservation zakomentowana w +`services/ollama/docker-compose.yml` (`f57a01a`); item trafił do backlogu +jako blokujący fazę mailową embeddingów (moduł 5). + +**Fix (2026-07-16).** Zainstalowany `nvidia-driver-595-open` z repo dystrybucji +(nie stary PPA `graphics-drivers` dla jammy — zdezaktywowany przez rename na +`.disabled`). RTX 4070 Ti SUPER 16GB, CUDA 13.2, `nvidia-smi` działa na +hoście. `nvidia-container-toolkit` był już obecny (doinstalowany jako +prerequisite przy cutoverze 07-15). GPU reservation przywrócona w compose. +Pomiar throughput GPU vs CPU baseline (0.79s/chunk) — patrz +`jobs/documents-ingest/README.md`, sekcja timing. + +**Status:** ZAMKNIĘTE. + +**Follow-upy pozostawione (osobne taski):** +- **Batching wywołań Ollamy** — przed fazą mailową (225k kopert). Sekwencyjne + wywołania `/api/embeddings` (nawet na GPU) będą wąskim gardłem przy takiej + skali; ocenić równoległość/batch API Ollamy. +- **`UNIQUE(envelope_id, chunk_index)` bez `model`** w `document_chunk` + (`services/kb-postgres/init/002_chunks.sql`) — re-embedding innym modelem + cicho no-opuje się przez istniejący constraint. Schema change do zrobienia + przy fazie 3 (patrz `jobs/documents-ingest/README.md`, sekcja "Idempotency" + kroku 6 embed). + diff --git a/kb/incidents/2026-07-22-ha-ken-cutover-legacy.md b/kb/incidents/2026-07-22-ha-ken-cutover-legacy.md new file mode 100644 index 0000000..474b56c --- /dev/null +++ b/kb/incidents/2026-07-22-ha-ken-cutover-legacy.md @@ -0,0 +1,50 @@ +--- +okf: "0.1" +type: incident +visibility: private +status: active +updated: 2026-08-03 +links: + - ../phases/backlog.md +--- + +## Cutover HA "ken": kontener piha to legacy, prawdziwy dom to RPi4/HAOS (2026-07-22) + +**Data**: 2026-07-22 +**Źródło**: recon — dwie instancje HA równolegle sterowały domem (kontener +`homeassistant5` na piha + RPi4 HAOS 192.168.31.7), patrz +`services/home-assistant/DESIGN.md` sekcja "Incident log". `instances.yaml` +naprawiony w tej samej sesji: `ken` = 192.168.31.7 (api), `ken-legacy` = +dawny kontener piha (docker-exec, archived). + +**Do zrobienia**: +1. **ha-diag-agent na piha**: przepiąć z `http://localhost:8123` (celuje w + legacy!) na `http://192.168.31.7:8123` — wymaga nowego tokenu + `diag_agent` wystawionego na instancji 31.7 (obecny token jest dla + kontenera piha i nie zadziała na nowym targecie). +2. **Wygaszenie `homeassistant5`**: import archiwalny do + `services/home-assistant/config/ken-legacy/` → `docker stop` (BEZ `rm`) + → 7 dni obserwacji (upewnić się, że nic w domu nie polega na tym + kontenerze) → decyzja o `docker rm`. +3. ✅ ZROBIONE (2026-07-22) — **Adapter `api` w `import.sh` dla `ken`**: + automatyzacje/skrypty/sceny przez `/api/config//config/` + (REST), dashboardy/area+entity registry/`input_*` helpery przez websocket + API (`scripts/ha/lib/ha_api.py`, `ha_ws.py`, `import_api.py`). Pierwszy + realny import `ken` zaimportował 118 automatyzacji, 5 skryptów, 3 sceny, + 7 dashboardów (default + 6 named; jeden zarejestrowany dashboard nigdy + nie skonfigurowany — `config_not_found`, odnotowany w raporcie, nie + twardy błąd). Pełny import `/config` pozostaje poza zasięgiem (HAOS bez + SSH) — patrz DESIGN.md. +4. ✅ ZROBIONE (2026-07-22) — **`scripts/ha/deploy.sh`, adapter `api`, zakres + automations/scripts/scenes**: drift-check (świeży re-import vs. `HEAD`, + dowolna różnica poza plikami z tego deployu = abort z diffem) → + walidacja (lokalny sanity check + `check_config` na instancji) → zapis + per obiekt (`POST /api/config//config/`) → verify (GET + + porównanie, bez auto-rollbacku). `--dry-run` zweryfikowany na żywym + `ken` (read-only, bez różnic). Testy offline: + `scripts/ha/tests/test_deploy_api_offline.sh`. Poza zakresem: dashboardy/ + helpery (WS, brak mutującej komendy), adapter `docker-exec`, DELETE + obiektów usuniętych z repo (tylko ostrzeżenie). + +--- + diff --git a/kb/phases/backlog.md b/kb/phases/backlog.md new file mode 100644 index 0000000..c8c87e7 --- /dev/null +++ b/kb/phases/backlog.md @@ -0,0 +1,71 @@ +--- +okf: "0.1" +type: phase +visibility: private +status: active +updated: 2026-08-03 +links: + - ../decisions/backlog-m1-solaria-prune-mitigation.md + - ../decisions/backlog-deploy-runner-instalacja.md + - ../decisions/backlog-disk-cleanup-zepsuty.md + - ../decisions/backlog-deploy-host-pusty.md + - ../incidents/2026-07-22-ha-ken-cutover-legacy.md + - ha-configs-as-code.md + - monitoring-floty-prometheus.md + - ../decisions/backlog-aktywne.md + - ../decisions/backlog-zamkniete.md + - ../decisions/backlog-anomaly-detection-liveness.md + - ../decisions/backlog-rozjazdy-repo-rzeczywistosc.md + - ../decisions/backlog-uid-gid-flota.md + - ../incidents/2026-07-12-observer-checkpoint-leksykalny.md + - ../incidents/2026-07-14-ha-diag-agent-node-unknown.md + - ../incidents/2026-07-12-deploy-local-ghost-kontenery.md + - ../incidents/2026-07-12-paperless-worker-config.md + - ../decisions/backlog-kb-serwisy-poza-monitoringiem.md + - ../incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md + - ../incidents/2026-07-16-ollama-solaria-brak-sterownika.md + - ../decisions/backlog-followupy-etap0.md +--- + +# Tech-debt backlog + +Centralny tracker tech-długu i znanych usterek. Wpisy ze sesji — dodawaj z datą i kontekstem. + +--- + + +## Spis rozbitych elementow + +> Ten plik byl monolitem 72 KB mieszajacym cztery typy OKF. W etapie 2 +> zostal rozbity per typ; ponizej spis powstalych dokumentow. Tresc +> pozycji nie byla redagowana — kazda sekcja `##` trafila do wlasnego +> pliku w calosci. + +### Decyzje / pozycje backlogu + +- [M1 aktywna na SOLARII — cleanup node-agenta wyłączony do czasu R1 (2026-08-04)](../decisions/backlog-m1-solaria-prune-mitigation.md) +- [deploy-runner: instalacja na węzłach + E2E redeployu (2026-08-03)](../decisions/backlog-deploy-runner-instalacja.md) +- [disk_cleanup w executorze jest zepsuty tak samo jak był redeploy (2026-08-03)](../decisions/backlog-disk-cleanup-zepsuty.md) +- [`scripts/deploy/deploy-host.sh` to pusty plik (2026-08-03)](../decisions/backlog-deploy-host-pusty.md) +- [Aktywne](../decisions/backlog-aktywne.md) +- [Zamknięte](../decisions/backlog-zamkniete.md) +- [Anomaly detection liveness — mózg uczy się wzorca dobowego per node (pomysł 2026-06-26)](../decisions/backlog-anomaly-detection-liveness.md) +- [Rozjazdy repo<->rzeczywistosc (z inwentaryzacji 2026-06-30)](../decisions/backlog-rozjazdy-repo-rzeczywistosc.md) +- [Tech-debt: globalny porządek uid/gid/uprawnień we flocie (2026-07-10)](../decisions/backlog-uid-gid-flota.md) +- [Nowe serwisy KB nie sa w monitoringu (desired-state)](../decisions/backlog-kb-serwisy-poza-monitoringiem.md) +- [Follow-upy z etapu 0 (truth cleanup, 2026-07-29)](../decisions/backlog-followupy-etap0.md) + +### Incydenty + +- [Cutover HA "ken": kontener piha to legacy, prawdziwy dom to RPi4/HAOS (2026-07-22)](../incidents/2026-07-22-ha-ken-cutover-legacy.md) +- [Bug: checkpoint observera po ścieżce leksykalnej — kruchy, zatruwa węzeł na zawsze (2026-07-12)](../incidents/2026-07-12-observer-checkpoint-leksykalny.md) +- [Bug: ha-diag-agent emituje eventy z node="unknown" do katalogu innego węzła (2026-07-14) — ZROBIONE (2026-07-15, `f2ba81b`)](../incidents/2026-07-14-ha-diag-agent-node-unknown.md) +- [Bug: deploy-local.sh control-plane pada na ghost-kontenerach i zostawia mózg rozłożony (2026-07-12)](../incidents/2026-07-12-deploy-local-ghost-kontenery.md) +- [Fix: paperless-worker@SOLARIA — dwa bugi configu, naprawione i zweryfikowane na żywo (2026-07-12)](../incidents/2026-07-12-paperless-worker-config.md) +- [Bug: deploy-node.sh nie przebudowuje obrazu — deploy "OK" ale nowy kod nie wchodzi (2026-07-15) — ✅ ZROBIONE (2026-07-16, commit `77defff`)](../incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md) +- [Ollama SOLARIA: brak sterownika NVIDII — ZAMKNIĘTE (2026-07-16)](../incidents/2026-07-16-ollama-solaria-brak-sterownika.md) + +### Fazy + +- [Nowy podprojekt: Home Assistant configs-as-code (szkielet)](ha-configs-as-code.md) +- [Plan: Monitoring floty — Prometheus jako źródło prawdy](monitoring-floty-prometheus.md) diff --git a/kb/phases/ha-configs-as-code.md b/kb/phases/ha-configs-as-code.md new file mode 100644 index 0000000..8fa07b7 --- /dev/null +++ b/kb/phases/ha-configs-as-code.md @@ -0,0 +1,23 @@ +--- +okf: "0.1" +type: phase +visibility: private +status: active +updated: 2026-08-03 +links: + - backlog.md +--- + +## Nowy podprojekt: Home Assistant configs-as-code (szkielet) + +**Data**: 2026-07-21 +**Branch**: `task/ha-skeleton` + +Szkielet struktury dla `services/home-assistant/` — configs-as-code dla +instancji HA (`ken` na PIHA, `chelsty-ha`). Na razie tylko struktura + +read-only import (`scripts/ha/import.sh`), bez deployu. Fazowanie, wybór +adaptera per instancja, model sync i otwarte pytania — +`services/home-assistant/DESIGN.md`. + +--- + diff --git a/kb/phases/monitoring-floty-prometheus.md b/kb/phases/monitoring-floty-prometheus.md new file mode 100644 index 0000000..9db151f --- /dev/null +++ b/kb/phases/monitoring-floty-prometheus.md @@ -0,0 +1,53 @@ +--- +okf: "0.1" +type: phase +visibility: private +status: active +updated: 2026-08-03 +links: + - backlog.md +--- + +## Plan: Monitoring floty — Prometheus jako źródło prawdy + +**Data**: 2026-06-22 +**Źródło**: sesja 2026-06-22 (`docs/sessions/2026-06-22.md`) +**Decyzja**: Prometheus (pull, `up{}`) zastępuje warstwę WYKRYWANIA liveness +(node-agent shipper + rsync/ssh + event-store + observer prune/checkpoint + ręczne TTL) +— przyczynę nawracających awarii (uid≠1000, ślepy ssh-mount, bloat ~242k eventów, +race prune↔checkpoint, NOMINAL-bez-TTL). Osobny fleet-Prometheus pod GitOps, **nie** +adopcja domowego instance PIHA. **BEZ** Alertmanagera — alert przez brain-watchdog. +Placement: VPS. Granica: zostają supervisor (remediacja), observer/panel, ha-diag-agent, +historia incydentów, out-of-band watchdog. +> Zastępuje wcześniejszy szkic (blackbox + Alertmanager) z sesji 2026-06-17. + +**Kroki (priorytetowo)**: +1. Scaffold serwisu `fleet-prometheus` pod GitOps (worktree `task/fleet-prometheus`, + wzorzec `services/vikunja/`): compose + `env.example` + `service.yaml` + README + + `healthcheck.sh`; rejestracja w `hosts/vps/services.yaml` + `inventory/topology.yaml`; + exposure `tailscale-internal`; pusty scrape na start (self + lokalny `node_exporter` VPS). +2. ✅ ZROBIONE (2026-06-26, commit `7d4014e`) — Inwentaryzacja nodów floty `100.x` do + scrape. Dodane: piha/solaria/lustro (`node:` label), vps zachowany. Saturn pominięty + (workstation), chelsty/chelsty-infra pominięte (node_exporter down z VPS → osobny wpis). +3. Container-layer exporter (cAdvisor lub lekki docker-state) — `node_exporter` nie widzi + kontenerów. +4. ✅ ZROBIONE (2026-06-30, commit `d417000`) — Reguły liveness (`up==0 for: 5m`). + `rules/liveness.yml`: `NodeDown expr up{node=~"vps|piha"}==0 for 5m severity critical`. + Only always-on (vps, piha); solaria/lustro świadomie wykluczone (intermittent → anomaly + detection). Deploy: fleet-prometheus Recreated (zmiana compose), reguła inactive=poprawnie. +5. ✅ ZROBIONE (2026-06-30, commit `62d6fc0`) — brain-watchdog: drugie wejście — poll + Prometheus `/api/v1/alerts` (`firing`) → Telegram. Architektura A: dwa niezależne tory, + mózg NIETKNIĘTY, debounce per-alert (klucz alertname:node w state.json). 12 testów pass. + PENDING zamknięty 2026-07-02: poll POTWIERDZONY reconem (obraz zbudowany po `62d6fc0`, + `PROMETHEUS_URL` w `.env` i w env kontenera, zero poll failed) — patrz + `docs/infra/inventory-verify-2026-07-02.md`. + ✅ END-TO-END UDOWODNIONE 2026-07-06 (Etap 0 cutoveru): tor Prometheus → + watchdog → Telegram potwierdzony w produkcji testem `AlertTestEtap0` + (firing → log polla → Telegram → rollback) — patrz `docs/sessions/2026-07-06.md`. +6. Rotacja tokenu HAOS w domowym prom (plaintext). +7. Przepięcie observer / panel `agents.okit.pl` na Prometheus jako źródło — największy + znak zapytania przy cutoverze. +8. Parallel-run obok rury eventowej; cutover dopiero gdy Prometheus-truth się udowodni. + +--- +