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) <noreply@anthropic.com>
12 KiB
| okf | type | visibility | status | updated | links | |
|---|---|---|---|---|---|---|
| 0.1 | decision | private | active | 2026-08-03 |
|
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/<node>/<action_id>.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/<node>/ 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-importpo 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: <hash>_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/<svc>/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/<svc>/.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)