Compare commits

...

30 commits

Author SHA1 Message Date
oskar 32b3643b8a chore(kb): usuniecie docs/questions.md
Stub z 2026-04-15 (65 linii) z pierwszego reconu, gdy homelab byl jednym
RPi5. Wszystkie "Unknown / needs clarification" dawno odpowiedziane przez
kb/subsystems/fleet-inventory.md i hosts/<node>/capabilities.yaml.

Kasowany, a nie migrowany, bo niesie publiczne IPv4/IPv6 i Tailscale IP
VPS-a przy zerowej wartosci merytorycznej — migracja oznaczalaby
przeniesienie tych danych do KB bez zadnego zysku.

Zero odwolan w repo. Tresc pozostaje w historii gita.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:26:02 +02:00
oskar a6cdc2356b fix(kb): README-wskazniki dla services i hosts + wyjatek ken-legacy
Naprawa kontraktu CLAUDE.md §Service Structure (opcja b). Migracja do KB
zabrala README z katalogow serwisow i hostow, przez co 0/26 katalogow
services/ spelnialo wymagany layout. Wskazniki przywracaja nawigacje,
nie duplikujac tresci.

31 wskaznikow, jednolity format, dokladnie 5 linii:

    # <nazwa>

    <jedno zdanie opisu>

    Dokumentacja: [kb/...](../../kb/...)

Opis nie jest pisany od zera — wyciagany z kb-doca: pierwsze pelne zdanie
pierwszego akapitu (sklejane z zawinietych linii, ciete tylko tam, gdzie
backticki i nawiasy sa zbilansowane), a dla node'ow czlon tytulu H1 po
myslniku. Dla ha-mcp opis z H1, bo pierwszy akapit zaczyna sie od markera
statusu. Wiodace markery "**Status: ...**" sa zdejmowane.

26 x services/<svc>/README.md, 5 x hosts/<node>/README.md.

WYJATEK services/home-assistant/config/ken-legacy/README.md: pelne
ostrzezenie "historical archive, do not deploy" przywrocone doslownie
z historii (odzyskane z drzewa sprzed migracji) + link do kb-doca.
Ostrzezenie musi stac tam, gdzie chroni — w katalogu archiwum, nie tylko
w KB. Odwolanie do services/home-assistant/DESIGN.md przepiete na
kb/decisions/ha-configs-as-code.md + kb/incidents/2026-07-22-ha-dwie-instancje.md.

check_okf.py: POINTER_GLOBS + is_pointer() wykluczaja wskazniki ze scope'u
lintu. Wskazniki celowo NIE maja frontmattera OKF — to nawigacja, nie
dokumenty KB. Wykluczenie zapisane wprost, zeby poszerzenie SCOPE nie
zaczelo ich nagle walidowac.

Bez wskaznikow: hosts/chelsty-ha/ i hosts/lustro/ — nie maja dokumentow
w kb/nodes/ (luka odnotowana juz w reconie etapu 1). Utworzenie ich
wymagaloby napisania nowej dokumentacji, czyli wyjscia poza konwersje.

Lint: 190/190 ZGODNE. Weryfikacja 822 plikow: 0 martwych linkow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:25:26 +02:00
oskar 6dffa5c565 fix(kb): przepiecie wszystkich odwolan wewnetrznych po migracji
126 plikow (md, yaml, sh, py) odwolywalo sie do sciezek sprzed migracji.

  15  markdown-linkow [..](..) -> policzona sciezka WZGLEDNA wobec pliku
      odsylajacego (wczesniej czesc z nich byla repo-root-relative i nie
      rozwiazywala sie z katalogu, w ktorym lezala)
 200  odwolan tekstowych (backticki, proza, yaml, importy w kodzie)
      -> nowa sciezka repo-root-relative, zgodnie z konwencja repo
   5  linkow rodzenstwa (gole nazwy plikow, np. "](DEPLOY.md)") — dzialaly
      tylko w starym katalogu; przeliczone recznie

Objete m.in.: CLAUDE.md (scripts/onboard/README.md -> kb/runbooks/
node-onboarding-tool.md, docs/backlog.md -> kb/phases/backlog.md),
README.md, .claude/skills/, 20 session logow, kod jobow.

Ostatnie 5 odwolan pochodzi z tresci wciagnietej rebasem z origin/master
(session log 2026-07-31, override node-agenta na SOLARII, dwie pozycje
backlogu) — wskazywaly na docs/incidents/, docs/kb/modules/ i
services/narty27/README.md sprzed migracji.

Dodany wzajemny link miedzy kb/services/control-plane.md (stub kodu)
a kb/subsystems/control-plane.md (opis, deprecated) — dwa dokumenty o tym
samym systemie, latwe do pomylenia.

Weryfikacja na 790 plikach: 0 odwolan do starych sciezek,
0 martwych linkow markdown. Lint OKF: 190/190 plikow ZGODNE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:21:16 +02:00
oskar 8d601e14b0 feat(kb): SPLIT backlog.md (72 KB) -> 19 dokumentow + cienki indeks
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>
2026-08-04 16:20:40 +02:00
oskar ff56a3430c feat(kb): 5 audytow/reconow -> kb/audits/ (type: audit, as_of)
czujniki-2026-07-30 (node-agent vs stability-agent)
lustro-shipping-2026-07-16 (event=dead prom=up, 1507 mismatchy)
prometheus-cutover-2026-07-06 (recon starego toru livenesci)
piha-slim-2026-07-02 (audyt odchudzania PIHA)
vps-stacki-2026-07-27 (audyt niezarzadzanych stackow na VPS)

ODSTEPSTWO OD RECONU — swiadome. Recon typowal te 5 plikow jako SPLIT
(audit+decision / audit+incident / audit+phase). Rozstrzygniecie 2 wprowadza
typ `audit` z polem as_of i mapuje kazdy z nich na JEDNA sciezke
kb/audits/<obszar>-<data>.md. Audyt jest spojna migawka stanu z konkretna
data — rozbicie go na "ustalenia" i "rekomendacje" rozerwaloby ten kontekst
i wymagaloby redakcji tresci, czego etap 2 zabrania. Zostaja w calosci.

Efekt: 29 SPLIT-ow z reconu realizowane jako 24 (10 service+runbook,
14 wielotypowych), 5 zamienionych na caloscowe dokumenty type: audit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar cb48ca0f94 feat(kb): SPLIT kb-00-overview -> subsystem + decision
kb/subsystems/kb-overview.md — architektura 4 warstwy x 4 filary, zasady
  przekrojowe, kolejnosc projektow, stan per zrodlo, konwencje katalogow
kb/decisions/kb-log-decyzji.md — sekcja "Decyzje — zamkniete vs otwarte"

Tresc sekcji nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar b996c9c774 feat(kb): SPLIT stability-agent-rollout -> runbook + subsystem
kb/runbooks/stability-agent-rollout.md — Deployment, Verification, Troubleshooting
kb/subsystems/stability-agent-architektura.md — Architecture Summary +
  "Why UI only showed CHELSTY"

Tresc sekcji nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar 136bdb563b feat(kb): SPLIT vps-control-plane -> subsystem (deprecated) + runbook
kb/subsystems/control-plane.md — status: deprecated,
  superseded_by: "przepisany tor redeploy, commity da151fc/79bfe8c 2026-08-03"
kb/runbooks/control-plane-deploy-recovery.md — Deployment + Recovery

Rozstrzygniecie 3: dokument NIE jest odswiezany, tylko oznaczony jako
nieaktualny. Ostatnia zmiana tresci 2026-05-27, czyli przed przepisaniem
toru redeployu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar c0fa8d83fc feat(kb): SPLIT lifecycle -> subsystem + runbook
kb/subsystems/service-lifecycle.md (visibility private wg rozstrzygniecia 6)
kb/runbooks/service-operational-recovery.md — Operational Recovery

Tresc sekcji nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar b90b60bee4 feat(kb): SPLIT deployment -> subsystem + incident
kb/subsystems/deployment.md — konwencje deployu
kb/incidents/deploy-sh-vps-niszczy-control-plane.md — sekcja
  "ZNANY BUG — deploy.sh vps niszczy control-plane (2026-06-25)"

UWAGA: "Recovery Workflow" to ### zagniezdzone w "Staged Deployment
Framework". Split mechaniczny tnie wylacznie po ##, a wyciagniecie tego
fragmentu wymagaloby przebudowy tresci — zostaje w dokumencie glownym.
Do rozwazenia jako osobny runbook w etapie redakcyjnym.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar fd6b6a547a feat(kb): SPLIT chelsty-runtime -> node + runbook
kb/nodes/chelsty-infra.md — runtime layout, SLZB-06U, ograniczenia sieciowe,
  lokalizacja configu Z2M, chelsty-ha bez node-agenta, backup sets
kb/runbooks/chelsty-deploy-recovery.md — Deployment Flow + Recovery Procedures

Tresc sekcji nietknieta; kontrola multizbioru linii == oryginal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar 93485be1ae feat(kb): SPLIT documents-ingest (30 KB) -> service + phase + runbook
kb/services/job-documents-ingest.md — opis jobu i mechanizmow
  (candidate selection, matching, consume/, idempotency, dry-run)
kb/phases/kb-m5-documents-ingest-fazy.md — faza 2, faza 2 krok 6,
  faza 3 krok 4, faza 3 krok 5 (4 sekcje fazowe wtopione w README)
kb/runbooks/documents-ingest-run.md — Usage, Verifying in Paperless, Tests

Najwiekszy README w repo. Tresc sekcji nietknieta; kontrola multizbioru
linii == oryginal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar 56caf4f745 feat(kb): SPLIT home-assistant/DESIGN.md -> decision + incident
kb/decisions/ha-configs-as-code.md — decyzje projektowe HA configs-as-code
kb/incidents/2026-07-22-ha-dwie-instancje.md — sekcja "Incident log":
dwie instancje HA sterujace domem rownolegle po migracji

Incydent byl dotad wtopiony w dokument decyzyjny; teraz jest adresowalny
jako osobny wpis type: incident. Tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar 627f98e8da feat(kb): SPLIT deploy-runner -> service + decision + runbook
kb/services/job-deploy-runner.md (How it works now)
kb/decisions/deploy-runner-uzasadnienie.md (What was broken)
kb/runbooks/deploy-runner-install.md (Install per node, Operating it, Tests)

Tresc sekcji nietknieta; kontrola multizbioru linii == oryginal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar a8bb738751 feat(kb): SPLIT paperless -> service + decision + runbook
kb/services/paperless.md (Stack, OIDC, Storage i backup, RAM na PIHA)
kb/decisions/paperless-split-ocr.md (split OCR serwis@PIHA + worker@SOLARIA)
kb/runbooks/paperless-cutover.md (cutover checklist)

Tresc sekcji nietknieta; kontrola multizbioru linii == oryginal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar 8b1a84064f feat(kb): SPLIT nextcloud -> service + decision + runbook
kb/services/nextcloud.md (Stack, WebDAV dla ingestu, Storage i backup)
kb/decisions/nextcloud-host-piha.md (decyzja: host = PIHA)
kb/runbooks/nextcloud-cutover.md (OIDC przez Forgejo + cutover checklist)

Tresc sekcji nietknieta; kontrola multizbioru linii == oryginal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar f869f18dcd feat(kb): SPLIT gokapi -> service + decision + runbook
kb/services/gokapi.md (Stack, Backup, Rejestracja w repo)
kb/decisions/gokapi-storage-e2e-siec.md (storage lokalny zamiast S3,
szyfrowanie E2E, bind tylko na Tailscale)
kb/runbooks/gokapi-cutover.md (cutover checklist)

Tresc sekcji nietknieta; kontrola multizbioru linii == oryginal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:31 +02:00
oskar e642f531f5 feat(kb): SPLIT fleet-prometheus -> service + decision + runbook
kb/services/fleet-prometheus.md (Stack, Placement & exposure, Data, Next steps)
kb/decisions/fleet-prometheus-osobny-od-prom.md ("Why separate from the home prom")
kb/runbooks/fleet-prometheus-deploy.md (Configuration, Verify)

Wzajemne links. Tresc sekcji nietknieta; kontrola: multizbior niepustych
linii czesci == oryginal z HEAD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 0b18d2fc3b feat(kb): SPLIT service+runbook — 10 serwisow -> 20 dokumentow
Wzorzec mechaniczny: sekcje deploy/verify/install/testy wycinane do
kb/runbooks/<serwis>-*.md, reszta zostaje dokumentem type: service.
Wzajemne `links` w obie strony. Tresc sekcji nietknieta — przenoszone
doslownie, dodany wylacznie naglowek H1 nowego runbooka.

kb-query, paperless-worker, planner-agent, ha-diag-agent, ollama-piha,
narty27, home-assistant, ha-mcp, job-gmail-header-backfill, job-mail-body-ingest.

Weryfikacja: dla kazdego pliku multizbior niepustych linii
(main + runbook) == oryginal z HEAD. Zero zgubionych, zero dodanych.

Recon szacowal 13 splitow service+runbook; faktycznie 2-typowych jest 10,
pozostale 5 (paperless, nextcloud, gokapi, fleet-prometheus, deploy-runner)
sa 3-typowe i ida osobno jako splity wielotypowe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 246101e8c5 feat(kb): stuby dla katalogow z kodem bez README (8 plikow)
Rozstrzygniecie 5: katalogi z kodem, ktore nie mialy README, dostaja stub
(frontmatter + stub: true + opis jednozdaniowy). NIE pisana pelna dokumentacja.

kb/services/: control-plane, node-agent, brain-watchdog, node-exporter,
job-gmail-bulk-import, pkg-kb-mail, pkg-kb-retrieval.

Kazdy stub podaje zrodlo opisu (service.yaml / docstring / CLAUDE.md /
pyproject.toml). Dla gmail-bulk-import i kb-retrieval zrodla brak — stub
mowi o tym wprost zamiast zmyslac opis.

Dodatkowo kb/subsystems/repo-operating-contract.md — wskaznik na CLAUDE.md
(rozstrzygniecie 4). CLAUDE.md zostaje w korzeniu jako zywa konfiguracja
narzedzia; kb-doc niesie pole contradicts: brak katalogow services/joplin,
services/outline, services/ai-cluster deklarowanych w sekcji
"Repo-managed services on VPS". Kontekst PR2 feat/vps-service-migration,
swiadomie nienaprawiane.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 2a21dd8c19 feat(kb): przenosiny type=audit do kb/audits/ (2 pliki, bez SPLIT)
Nowy typ `audit` (rozstrzygniecie 2) — migawka stanu z pola `as_of`,
nie opis stanu biezacego.

monitoring-coverage-2026-07-14, ha-automatyzacje-2026-07-23.
Pozostale 5 audytow/reconow jest wielotypowych — wychodza w grupie SPLIT-ow.

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 1275c2532a feat(kb): przenosiny type=phase do kb/phases/ (14 plikow, bez SPLIT)
Plany faz KB (modul 5 fazy 2/3/4/mailowa, moduly 0/2/3/4, documents-ingest,
raport fallback-dedup, eval retrieval-pilot) oraz prometheus-cutover-etap2,
subsystem-a-naprawa, okit-cloudflare.

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar c66f3db708 feat(kb): przenosiny type=runbook do kb/runbooks/ (5 plikow, bez SPLIT)
ollama-solaria-cutover, node-onboarding-tool (ze scripts/onboard/README.md),
ha-diag-agent-deploy, npm-api (ze scripts/npm/README.md),
node-onboarding (public).

UWAGA: scripts/onboard/README.md jest linkowany z CLAUDE.md — odwolanie
naprawiane w grupie 7.

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 83f9733d1b feat(kb): przenosiny type=incident do kb/incidents/ (1 plik)
Jedyny jawny incident-doc w repo. Pozostale incydenty siedza wtopione
w docs/backlog.md, services/home-assistant/DESIGN.md i lustro-shipping-recon
— wychodza w grupie SPLIT-ow.

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar fff54ffa6d feat(kb): przenosiny type=decision do kb/decisions/ (5 plikow, bez SPLIT)
architektura-2026-07-28, ai-cluster-legacy (public), sso-forgejo-oidc,
kb-dokumenty-otwarte (status: planned — decyzje jeszcze niepodjete),
tech-debt-legacy (deprecated, przejete przez backlog).

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar a427aad47e feat(kb): przenosiny type=subsystem do kb/subsystems/ (18 plikow, bez SPLIT)
public (wzorce/schematy, bez IP/portow/sciezek hostow): observer,
capability-model, event-system, standards, agent-operating-procedures,
service-model, action-approval-model.

private: recon-multiagent, fleet-inventory, fleet-inventory-verify,
kb-mail-pillar, kb-documents-pillar, topology, agent-system.

deprecated (martwe stuby z 2026-04-15) — visibility private wg
rozstrzygniecia 6: access-model, core-stack, legacy-services-list, networking.

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 93a9516907 feat(kb): przenosiny type=service do kb/services/ (13 plikow, bez SPLIT)
Serwisy o jednorodnej tresci (bez wydzielonej sekcji deploy/runbook):
kb-postgres, stability-agent, llm-gateway, vikunja, zigbee2mqtt, npm,
forgejo, ollama, ir-ac-ha-integration, chelsty-stability-agent.

Deprecated: mosquitto (NOT DEPLOYED/LEGACY), home-assistant-ken-legacy
(archiwum importu), joplin (stub z 2026-04-15, brak katalogu services/joplin/).

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 5bde0a9168 feat(kb): przenosiny type=node do kb/nodes/ (6 plikow)
hosts/{piha,saturn,solaria,vps}/README.md -> kb/nodes/<node>.md
docs/hetzner-vps.md -> kb/nodes/hetzner-vps.md (deprecated, stub z 2026-04-15)
docs/hardware.md    -> kb/nodes/legacy-hardware.md (deprecated)

git mv + frontmatter, tresc nietknieta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 7a2d7bdee3 feat(kb): frontmatter OKF dla 39 session logow
Session logi zostaja w docs/sessions/ (decyzja z etapu 1). Dodany wylacznie
blok frontmattera: type: session-log, visibility: private, status: active,
updated = data ostatniego commita pliku.

Tresc nietknieta — kazdy plik to +9/-0 linii.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
oskar 87e172ea27 feat(kb): walidator OKF v0.1 — scripts/kb/check_okf.py
Zaadaptowany z ~/narty-2027/saalbach-kb/check_okf.py. Tamten sprawdzal
wylacznie obecnosc frontmattera i niepuste `type`. Tutaj dochodza reguly
tego repo: okf przypiete do "0.1", type/visibility/status z zamknietych
list, updated/as_of jako YYYY-MM-DD, as_of wymagane wylacznie dla
type: audit, superseded_by wymagane wylacznie dla status: deprecated,
stub jako bool, links rozwiazywalne wzgledem katalogu dokumentu.

Zakres walidacji: kb/ + docs/sessions/. Reszta repo (CLAUDE.md, README.md,
.claude/skills/) lezy poza baza wiedzy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00
281 changed files with 6673 additions and 4275 deletions

View file

@ -93,7 +93,7 @@ services:
preflight fills `arch`, `ram_mb`, `docker_present`, `mm_runtime` — do NOT guess these.
Full schema: `scripts/onboard/README.md`.
Full schema: `kb/runbooks/node-onboarding-tool.md`.
---

View file

@ -40,7 +40,7 @@ Pipeline stages: **prepare → validate → deploy → verify → diagnose (on f
## Node Onboarding
New nodes are onboarded via `scripts/onboard/` — an idempotent bash tool driven by
`hosts/<node>/node.yaml` manifests (no Ansible). See `scripts/onboard/README.md` for
`hosts/<node>/node.yaml` manifests (no Ansible). See `kb/runbooks/node-onboarding-tool.md` for
the full schema, step status table, and gotchas.
Key fields in `node.yaml`: `ssh_user`, `first_contact` (LAN IP — not `.local`),
@ -93,7 +93,7 @@ Agent → /opt/homelab/actions/pending/<id>.json
→ Executor dispatches to the target node → completed / failed
```
The executor never connects to a node (deliberate — see docs/backlog.md
The executor never connects to a node (deliberate — see kb/phases/backlog.md
"Remediacja floty bez SSH"). It writes a dispatch file that the node collects:
| Action type | Inbox | Executed on the node by |

View file

@ -31,29 +31,29 @@ Action approval flow: `pending/` → operator approves → `approved/` → execu
## Repository Structure
- `docs/`: [Infrastructure Standards](docs/standards.md) and [Deployment Conventions](docs/deployment.md).
- `docs/architecture/PLAN-subsystem-a-2026-07-28.md`: [Current Maintenance Plan (Control Plane)](docs/architecture/PLAN-subsystem-a-2026-07-28.md).
- `docs/`: [Infrastructure Standards](kb/subsystems/standards.md) and [Deployment Conventions](kb/subsystems/deployment.md).
- `kb/phases/subsystem-a-naprawa.md`: [Current Maintenance Plan (Control Plane)](kb/phases/subsystem-a-naprawa.md).
- `hosts/`: Host-specific configurations and service assignments.
- `services/`: Reusable Docker Compose service definitions.
- `scripts/`: Deployment and management scripts.
## Getting Started
1. **Standardization**: Follow the [Infrastructure Standards](docs/standards.md).
2. **Deployment**: See [Deployment Conventions](docs/deployment.md) for how to roll out changes.
1. **Standardization**: Follow the [Infrastructure Standards](kb/subsystems/standards.md).
2. **Deployment**: See [Deployment Conventions](kb/subsystems/deployment.md) for how to roll out changes.
3. **SATURN**: Remember that SATURN is the only node where commits should be made.
## Documentation Index
- [Current Maintenance Plan (Control Plane)](docs/architecture/PLAN-subsystem-a-2026-07-28.md)
- [Infrastructure Standards](docs/standards.md)
- [Agent Operating Procedures](docs/agents.md) (For AI/Non-Human Agents)
- [Deployment Conventions](docs/deployment.md)
- [Hardware](docs/hardware.md)
- [Networking](docs/networking.md)
- [Services](docs/services.md)
- [Node Capabilities](docs/capabilities.md)
- [Action Model](services/agent-system/action-model.md)
- [Current Maintenance Plan (Control Plane)](kb/phases/subsystem-a-naprawa.md)
- [Infrastructure Standards](kb/subsystems/standards.md)
- [Agent Operating Procedures](kb/subsystems/agent-operating-procedures.md) (For AI/Non-Human Agents)
- [Deployment Conventions](kb/subsystems/deployment.md)
- [Hardware](kb/nodes/legacy-hardware.md)
- [Networking](kb/subsystems/networking.md)
- [Services](kb/subsystems/legacy-services-list.md)
- [Node Capabilities](kb/subsystems/capability-model.md)
- [Action Model](kb/subsystems/action-approval-model.md)
---
*Note: This repository documents the state of the homelab. Runtime state lives outside the repository in `/opt/homelab`.*

File diff suppressed because it is too large Load diff

View file

@ -1,65 +0,0 @@
# Unknowns and Clarification Questions
## Description
This page lists information that is missing or unclear from the current homelab documentation.
## Current configuration
The currently documented configuration is limited to:
- Raspberry Pi 5 as the main server.
- Docker, Portainer, and Nginx Proxy Manager as the core stack.
- NAT with forwarded ports:
- `80-81` to `4480-4481`
- `443` to `4443`
- Public access through Nginx Proxy Manager with Let's Encrypt HTTPS.
- Private access through Tailscale.
- Hetzner VPS handoff:
- Hostname: `ubuntu-4gb-hel1-1`
- Tailscale IP: `100.95.58.48`
- Public IPv4: `135.181.153.108`
- Public IPv6: `2a01:4f9:c014:98f0::1`
- Running container: `npm`
- Joplin files created but not running.
## Known facts
- The homelab is documented only from the known facts above.
- Anything not listed as known remains unconfirmed.
## Unknown / needs clarification
1. What operating system and version is running on the Raspberry Pi 5?
2. What is the Raspberry Pi 5 RAM size?
3. What storage devices are used, and where is persistent service data stored?
4. What is the Raspberry Pi 5 LAN IP address?
5. Is the Raspberry Pi 5 using DHCP or a static IP address?
6. What router or firewall performs NAT and port forwarding?
7. Is the WAN IP static, dynamic, or behind CGNAT?
8. Does external port `80` map to internal port `4480`, and does external port `81` map to internal port `4481`?
9. Are the forwarded ports TCP only, UDP only, or both?
10. Are any other ports forwarded?
11. What domain names or subdomains point to the homelab?
12. What are the Nginx Proxy Manager proxy hosts?
13. Which services are public, and which are private-only?
14. Is HTTP-to-HTTPS redirection enabled in Nginx Proxy Manager?
15. Are Nginx Proxy Manager access lists used?
16. How are Docker, Portainer, and Nginx Proxy Manager deployed?
17. Are Docker Compose files, Portainer stacks, or other manifests available?
18. What containers are currently running?
19. What Docker networks and volumes exist?
20. What is the Tailscale device name for the Raspberry Pi 5?
21. Does the Raspberry Pi 5 advertise Tailscale subnet routes?
22. Is the Raspberry Pi 5 configured as a Tailscale exit node?
23. Is Tailscale SSH enabled?
24. What backup system exists, if any?
25. What monitoring or alerting exists, if any?
26. Is the Hetzner VPS part of the homelab documentation scope, a separate system, or both?
27. What is the operating system version on `ubuntu-4gb-hel1-1`?
28. Is public Nginx Proxy Manager admin access on port `81` intentionally reachable on `135.181.153.108`?
29. Has DNS record `joplin.okit.pl -> 135.181.153.108` been created?
30. Has optional AAAA record `joplin.okit.pl -> 2a01:4f9:c014:98f0::1` been created?
31. Has `POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD` been changed before first Joplin production start?
32. Has the Nginx Proxy Manager proxy host for `joplin.okit.pl` been created?
33. Are ports `80` and `443` publicly reachable on the Hetzner VPS for Let's Encrypt HTTP validation?

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-05-27
links: []
---
# SESSION: Budowa planner-agent — LLM-based diagnostics
**DATA:** 2026-05-27

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-05-27
links: []
---
# SESSION: Stabilizacja systemu wieloagentowego homelabu
**DATE:** 2026-05-27

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-09
links: []
---
# Sesja 2026-06-08 — onboarding LUSTRO (RPi4 / Magic Mirror / KEN)
## Cel
@ -81,7 +90,7 @@ przez Tailscale działa bezhasłowo. Verify czysty (arch=aarch64).
## Learnings
(odzwierciedlone też w `scripts/onboard/README.md`)
(odzwierciedlone też w `kb/runbooks/node-onboarding-tool.md`)
- mDNS `.local` zawodny do automatyzacji → `first_contact` przez IP lub tailscale, nie `.local`
- istniejący node z userem uid=1000: użyj go zamiast tworzyć `oskar` (kolizja uid)

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-09
links: []
---
# Sesja 2026-06-09 — flota recovery + LUSTRO register
## Cel
@ -121,4 +130,4 @@ Docelowo: osobny worktree per task.
## Tech-debt złapany w sesji
→ wpisany do `docs/backlog.md`
→ wpisany do `kb/phases/backlog.md`

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-11
links: []
---
# Sesja 2026-06-10/11 — lustro SSH shipping fix + ha-diag-agent piha
## Cel
@ -70,13 +79,13 @@ solaria / piha / chelsty to wciąż **stare root kontenery** node-agenta
(piha Created 2026-05-27, uid 0). Ich mount `/root/.ssh` działa tylko dlatego,
że kontenery są sprzed `user: "1000:1000"`. Pierwszy `--force-recreate` / reboot
hosta / update obrazu przełączy je na uid 1000 i shipping padnie jak na lustrze.
**NIE RECREATE bez fixu.** Szczegóły i fix: `docs/backlog.md`.
**NIE RECREATE bez fixu.** Szczegóły i fix: `kb/phases/backlog.md`.
---
## Tech-debt złapany w sesji
→ wpisany do `docs/backlog.md` (flota-bomba, ha-diag-agent blocked,
→ wpisany do `kb/phases/backlog.md` (flota-bomba, ha-diag-agent blocked,
poison-quarantine review, `--omit-dir-times`, stale komentarz node_agent.py,
shipping success na `logger.debug`, event-bloat lustro na VPS).
@ -87,8 +96,8 @@ fa59625 docs(ha-diag-agent): replace curl verify commands with docker exec
d7e0d31 fix(ha-diag-agent): remove host port mapping for 8087
### Files changed
services/ha-diag-agent/DEPLOY.md | 4 ++--
services/ha-diag-agent/README.md | 4 ++--
kb/runbooks/ha-diag-agent-deploy.md | 4 ++--
kb/services/ha-diag-agent.md | 4 ++--
services/ha-diag-agent/docker-compose.yml | 3 ---
services/ha-diag-agent/service.yaml | 3 ---
4 files changed, 4 insertions(+), 10 deletions(-))

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-17
links: []
---
# Sesja 2026-06-17 — KB foundations (etap 1 maili)
## Cel
@ -111,14 +120,14 @@ services/kb-postgres/service.yaml
services/kb-postgres/env.example
services/kb-postgres/healthcheck.sh
services/kb-postgres/init/001_envelope.sql
services/kb-postgres/README.md
kb/services/kb-postgres.md
hosts/solaria/runtime/kb-postgres/docker-compose.override.yml
hosts/solaria/services.yaml
inventory/topology.yaml
packages/kb-mail/pyproject.toml
packages/kb-mail/src/kb_mail/{__init__,envelope,db,archive}.py
packages/kb-mail/tests/{conftest,test_envelope,test_archive,test_db,test_migration}.py
docs/kb/kb-00-overview.md (etap 1 done, konwencja packages/)
kb/subsystems/kb-overview.md (etap 1 done, konwencja packages/)
CLAUDE.md (sekcja Shared Python Libraries)
docs/sessions/2026-06-17-kb-foundations.md
```

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-17
links: []
---
# Sesja 2026-06-17 — Vikunja OIDC+GitOps · Observer heartbeat-TTL · panel-source
## Zrobione i wdrożone

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-22
links: []
---
# Sesja 2026-06-22 — KB spine relokowany na PIHA + przygotowanie hosta
## Cel

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-22
links: []
---
# Sesja 2026-06-22 — decyzja: Prometheus jako źródło prawdy dla liveness floty
## Cel

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-24
links: []
---
# Sesja 2026-06-24 — KB etap 2: importer Gmail (kod gotowy)
## Cel
@ -162,7 +171,7 @@ jobs/gmail-bulk-import/pyproject.toml
jobs/gmail-bulk-import/tests/test_importer.py
hosts/piha/capabilities.yaml
.gitignore
docs/kb/kb-00-overview.md (etap 2 gotowy, konwencja jobs/)
docs/kb/kb-01-email-design.md (§8 krok 2 = kod gotowy)
kb/subsystems/kb-overview.md (etap 2 gotowy, konwencja jobs/)
kb/subsystems/kb-mail-pillar.md (§8 krok 2 = kod gotowy)
docs/sessions/2026-06-24-kb-gmail-importer.md
```

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-24
links: []
---
# Sesja 2026-06-24 — fleet-prometheus etap 1: scaffold + deploy-node hostname fix
## Cel
@ -99,7 +108,7 @@ docker compose \
---
## Nowe tech-debty (dodane do `docs/backlog.md`)
## Nowe tech-debty (dodane do `kb/phases/backlog.md`)
1. **Rozjazd stanu Docker Compose na VPS** — serwisy `node-agent`, `control-plane` i inne
stworzone innym `project-name` niż `deploy-node.sh` oczekuje; Recreate pada na stale

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-26
links: []
---
# Sesja 2026-06-25 — KB etap 2: bulk import Gmail uruchomiony
## Cel

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-25
links: []
---
# Sesja 2026-06-25 — fleet-prometheus etap 1: uruchomienie na VPS + incydent mózgu
## Cel

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-26
links: []
---
# Sesja 2026-06-26 — fleet-prometheus etap 2: targety floty + zamknięcie buga deploy.sh vps
## Cel

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-30
links: []
---
# Sesja 2026-06-30 — Inwentaryzacja floty + gaszenie dysku SATURN
## Cel
@ -10,7 +19,7 @@ architekturą dokumentów KB. Start od weryfikacji stanu faktycznego wszystkich
- CC (Sonnet 4.6) w worktree `fleet-inventory` zebrał stan faktyczny (docker ps +
free/df/nproc) z 4 dostępnych nodów: PIHA, VPS, SOLARIA, SATURN. LUSTRO+CHELSTY
offline (timeout :22) -> oznaczone UNREACHABLE.
- Wynik: `docs/infra/inventory-2026-06-30.md` — 23 zpriorytetyzowane rozjazdy.
- Wynik: `kb/subsystems/fleet-inventory.md` — 23 zpriorytetyzowane rozjazdy.
- Kluczowe ustalenia:
- **forgejo** biega na PIHA (always-on), ale `service.yaml owner_node=saturn` — rozjazd
- **mosquitto** biega na VPS, `service.yaml owner=piha`, na PIHA go nie ma

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-30
links: []
---
# 2026-06-30 — Migracja kapala.org → Cloudflare + wildcard DNS-01, HA i Immich na mesh
## Cel

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-30
links: []
---
# Sesja 2026-06-30 — fleet-prometheus liveness: reguły NodeDown + wpięcie watchdog→Prometheus
## Cel

View file

@ -1,7 +1,16 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-02
links: []
---
# Sesja 2026-07-02 — Modul 0 (odchudzenie PIHA) + migracja Forgejo/Vikunja na kapala.org
## Modul 0 kb-02 — WYKONANY (faza 1 audyt + faza 2 egzekucja)
- Audyt CC (read-only): docs/infra/piha-slim-audit-2026-07-02.md
- Audyt CC (read-only): kb/audits/piha-slim-2026-07-02.md
- Review Oskara skorygowal audyt: llm-gateway = WLASNY kod (FastAPI-router LLM,
/opt/llm-gateway, proxy do Ollama@SOLARIA) — NIE martwy; immich MUSI byc 24/7
na PIHA (SOLARIA sesyjna) — rekomendacja przeniesienia wykreslona.

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-02
links: []
---
# Sesja 2026-07-02 — recon-weryfikacja inwentaryzacji + rozbrojenie trzech min
## Cel
@ -12,7 +21,7 @@ Sesja tylko-recon + minimalne fixy; bez deployów nowych feature'ów.
### Recon-weryfikacja inwentaryzacji floty (commit `57a6dff`, read-only)
Wynik: `docs/infra/inventory-verify-2026-07-02.md`.
Wynik: `kb/subsystems/fleet-inventory-verify.md`.
**Bilans 23 rozjazdów z audytu 2026-06-30**:
- **20 wciąż aktualnych** — nic się samo nie naprawiło.
@ -91,7 +100,7 @@ Po jednej linii per plik; `owner_node` nie występował nigdzie indziej w repo.
nie rezolwuje z SOLARII; brak formalnego override mem_limit fleet-prometheus
w `hosts/vps/runtime/` (siedzi w bazowym compose — kosmetyka).
Wpisy dodane do `docs/backlog.md` w tej sesji.
Wpisy dodane do `kb/phases/backlog.md` w tej sesji.
---

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-06
links: []
---
# Sesja 2026-07-06 — cutover liveności na Prometheus: recon starego toru + Etap 0 udowodniony w boju
## Cel
@ -13,7 +22,7 @@ Prometheus → brain-watchdog → Telegram, którego brakowało od 2026-06-30.
### Recon cutoveru — wmergowany (commit `d94bb38`)
Wynik: `docs/infra/prometheus-cutover-recon-2026-07-06.md` (517 linii, read-only,
Wynik: `kb/audits/prometheus-cutover-2026-07-06.md` (517 linii, read-only,
zero zmian w kodzie). Kluczowe ustalenia:
- **Cutover to podmiana klasyfikacji liveności w JEDNYM miejscu**

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-07
links: []
---
# Session log 2026-07-07 — Immich upload fix / pimain cleanup (kontynuacja 2026-07-03)
## Kontekst

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-07
links: []
---
# Sesja 2026-07-07 — okit.pl Faza 1 (wildcard cert) + przepiecie 9 hostow
## Kontekst

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-09
links: []
---
# Sesja 2026-07-09 — KB configi (9 decyzji) + wzorzec dzielenia plikow (Nextcloud twierdza + Gokapi)
## Kontekst

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-10
links: []
---
# Sesja 2026-07-10 — Deploy 1 Paperless (DZIALA) + swap PIHA + npm-API tool w akcji
## Glowne osiagniecie: Paperless serwis LIVE

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-12
links: []
---
# Sesja 2026-07-12 — Deploy 2: split-host OCR-worker (DZIALA) + decyzja kierunku KB
## Deploy 2 — OCR-worker na SOLARIA przez NFS: DZIALA end-to-end
@ -37,7 +46,7 @@ maila = ta sama encja, DOWOD zasady kb-00 #7), (3) interfejs pytan (RAG) — pie
realnej uzytecznosci. Dopiero POTEM dopelniac importy (reszta Takeout, zdjecia, transakcje).
## TODO nastepne
- MODUL 5 (koperta + ingest + embeddingi + cross-source) — docs/kb/modules/05-documents-ingest.md
- MODUL 5 (koperta + ingest + embeddingi + cross-source) — kb/phases/kb-m5-documents-ingest.md
- Import probki zalacznikow z maili (kilkaset, nie 70k) — do zbudowania RAG
- Interfejs pytan / RAG — warstwa uzytkowa
- Deploy 3 (Nextcloud), Deploy 4 (Gokapi) — configi gotowe, czekaja

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-15
links: []
---
# Sesja 2026-07-15 — Prometheus cutover Etap 2 (analiza GO) + domknięcie rodziny bugów event-pipeline/checkpoint
## Kontekst
@ -18,12 +27,12 @@ parsowany z nazwy `evt-<node>-<ts>-...` (fallback mtime, **nigdy 0** dla
istniejącego pliku — 0 = leksykalne "starszy niż checkpoint" = dokładnie ten
poison), migracja starych path-checkpointów przy starcie. Zdeployowany na VPS
(observer `StartedAt` 07-14). Zweryfikowany dziś jako kompletny i zdeployowany.
Szczegóły: `docs/backlog.md` (sekcja "Bug: checkpoint observera po ścieżce
Szczegóły: `kb/phases/backlog.md` (sekcja "Bug: checkpoint observera po ścieżce
leksykalnej").
### 2. docs(infra) analiza Etapu 2 shadow-run (Fable, `8fec62d`)
`docs/infra/prometheus-shadow-etap2-analiza-2026-07-15.md` — 165 mismatchy
`kb/phases/prometheus-cutover-etap2.md` — 165 mismatchy
`SHADOW_LIVENESS_MISMATCH` solaria/lustro w dobie 2026-07-14 WYJAŚNIONE: dwa
nocne wyłączenia węzłów (lustro 21:30 UTC — regularny power-off, solaria
21:34 UTC). Wzorzec `event=fresh prom=down` to **nie** "żywy węzeł niewidziany

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-16
links: []
---
---
@ -22,7 +31,7 @@
### 1. RECON lustro shipping (Fable, commit `542bba4`)
`docs/infra/lustro-shipping-recon-2026-07-16.md` — 1507 mismatchy `lustro event=dead prom=up` w trwałym logu WYJAŚNIONE: (a) wczorajszy kontrolowany test (node-agent stał 3h20m, nie 15 min jak zakładano) + (b) poranny boot-race 56s. **Werdykt: shipping lustro działa, ZERO recurring problemu.** Prometheus 0 pomyłek w 48h — wzmacnia rekomendację GO dla Etapu 3 cutoveru.
`kb/audits/lustro-shipping-2026-07-16.md` — 1507 mismatchy `lustro event=dead prom=up` w trwałym logu WYJAŚNIONE: (a) wczorajszy kontrolowany test (node-agent stał 3h20m, nie 15 min jak zakładano) + (b) poranny boot-race 56s. **Werdykt: shipping lustro działa, ZERO recurring problemu.** Prometheus 0 pomyłek w 48h — wzmacnia rekomendację GO dla Etapu 3 cutoveru.
Znaleziska poboczne: lustro biega na obrazie sprzed 5 tyg (deploy-node bez `--build` — patrz fix #2 niżej); fake-hwclock boot-race (RPi bez RTC — pierwszy event po boocie ma stary stempel, dropnięty przez timestamp checkpoint).

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-20
links: []
---
# Sesja 2026-07-17/18 — KB faza 3: kroki 2-5 DOMKNIĘTE
## Krok 2 — migracja 004 + pilot streszczeń A/B (17.07)

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-22
links: []
---
# Sesja 2026-07-17…21 — wątek KB: faza 3 kroki 3-5 DONE (+ incydenty)
**Krok 3 — kaskada summary→chunk:** bramka **PASS at N=10, k=5** (N-sweep {1..20}: N=5 to zmierzona podłoga, N=10 niesie 2× margines); kaskada nie degraduje niczego, na 186 dok nie poprawia (test architektury pod skalę mailową, zgodnie z przewidywaniem planu); koszt +1 SQL, zero dodatkowych embedów. cascade_query = domyślna ścieżka kb-query; flat_query zostaje jako baseline. Eval przepisany do wersjonowanego eval/queries.yaml + retrieval_eval.py.

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-22
links: []
---
# 2026-07-22 — HA: incydent dwóch mózgów, cutover ken, archiwum legacy
## Odkrycie

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-22
links: []
---
# Sesja 2026-07-22 — KB faza 4: recon + kroki 1-2 (kb-query LIVE na PIHA)
**Recon+plan fazy 4** (05-faza4-plan.md, 603 linie): D1 wydzielenie packages/kb-retrieval (documents-ingest ciągnie anthropic+CLI — nie do obrazu serwisu); D2 fallback embed z pełną maszyną stanów, ale gate'owany kalibracją RAM na żywym PIHA (audyt nieaktualny, ~3.8Gi zajęte — plan daje kryteria i alternatywę: jawna degradacja 503 zamiast łamania inwariantu modelu); OIDC wbudowane w apkę (authlib, wzorzec repo — nigdy forward-auth); gmail: envelope_id już JEST Message-ID.

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-23
links: []
---
# 2026-07-22/23 — Control-plane: dziura w operator_ui + pierwszy pełny cykl remediacji bez SSH
Zamknięcie wielosesyjnego wątku "czemu mózg nie leczy floty". Dwa dni pracy,
@ -61,7 +70,7 @@ approval → executor → node-agent → docker restart → completed.
test E2E padł: agent rzucał `[Errno 13] Permission denied:
/opt/homelab/actions/dispatch` co cykl. Root cause to ZNANY, POWRACAJĄCY
(już 4. raz — patrz sekcja "Tech-debt: globalny porządek uid/gid/uprawnień
we flocie" w `docs/backlog.md`) motyw
we flocie" w `kb/phases/backlog.md`) motyw
uid/gid na PIHA: oskar ma uid 1004, kontener agenta biega jako uid 1000
(= user `pi` na hoście). `/opt/homelab/actions` było `oskar:oskar
drwxr-xr-x` (utworzone w maju), podczas gdy DZIAŁAJĄCY wzorzec to
@ -102,7 +111,7 @@ approval → executor → node-agent → docker restart → completed.
Pierwszy w historii systemu pełny cykl remediacji end-to-end potwierdzony w
produkcji (PIHA), bez SSH z control-plane do węzłów. Publiczna dziura
autoryzacji na `operator_ui.py:18180` zamknięta (bind ograniczony do
Tailscale). Otwarte follow-upy — patrz `docs/backlog.md` (retry-w-nieskończoność
Tailscale). Otwarte follow-upy — patrz `kb/phases/backlog.md` (retry-w-nieskończoność
zepsutego JSON, uprawnienia `actions/` na innych węzłach, brak twardego checka
`.env`/`TAILSCALE_BIND_IP` w `deploy-local.sh`, brak autoryzacji w
`operator_ui.py`, zapchana approval queue przez `alert_only`, brak

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-23
links: []
---
# 2026-07-22/23 — HA: adapter api, import ken, deploy.sh, otwarcie fazy 1
## Wykonane

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-23
links: []
---
# 2026-07-23 (cd.) — HA: klima E2E, audyt Fable, fix-pack 1
## Klima salonowa — pierwsza automatyzacja LLM przez repo (E2E)

View file

@ -1,6 +1,15 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-23
links: []
---
# Sesja 2026-07-23 — KB faza 4: ingress kb.kapala.org (krok 5/§8)
**Zakres**: wyłącznie ingress (`docs/kb/modules/05-faza4-plan.md` §8, krok 5) —
**Zakres**: wyłącznie ingress (`kb/phases/kb-m5-faza4.md` §8, krok 5) —
frontend i `/search` już LIVE na PIHA (port 8230) od sesji 2026-07-22. Zero
zmian w kodzie kb-query w tej sesji.
@ -61,8 +70,8 @@ w tej samej sesji, osobnym przebiegiem po zgłoszeniu przez operatora:
Potwierdzone w repo (zgodnie z `05-faza4-plan.md` §1.4): **brak wzorca
forward-auth/reverse-proxy-level auth** — NPM community edition go nie ma
(sprawdzone: brak `oauth2-proxy`/`authelia`/`forward_auth` w kodzie repo poza
wzmiankami "przyszła opcja" w `docs/kb/kb-02-documents-design.md` i
`hosts/vps/README.md`). Wszystkie 3 precedensy (paperless/nextcloud/vikunja)
wzmiankami "przyszła opcja" w `kb/subsystems/kb-documents-pillar.md` i
`kb/nodes/vps.md`). Wszystkie 3 precedensy (paperless/nextcloud/vikunja)
robią OIDC **wewnątrz aplikacji**. kb-query nie ma dziś żadnego logowania.
Zgodnie z instrukcją zadania: **nie budowano** nowego komponentu auth.
@ -113,7 +122,7 @@ username collision, `docs/sessions/2026-07-10-paperless-deploy.md`).
## Pliki repo zmienione
- `services/kb-query/README.md` — sekcja "Ingress" (co żyje, co nie, dlaczego
- `kb/services/kb-query.md` — sekcja "Ingress" (co żyje, co nie, dlaczego
auth odłożone) zastępuje starą notatkę "not wired up yet".
- `docs/sessions/2026-07-23-kb-f4-ingress.md` — ten dokument.

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-27
links: []
---
# 2026-07-27 — HA: legacy zamkniete, kasacje, pimirror, sonda Zigbee
## Wykonane

View file

@ -1,6 +1,15 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-30
links: []
---
# Sesja 2026-07-27 — KB faza 4: fallback embed SOLARIA→PIHA (krok 3, ostatni element rdzenia)
> **Dopisek redakcyjny (2026-07-30, dedup — `docs/kb/modules/05-fallback-dedup-raport.md`):**
> **Dopisek redakcyjny (2026-07-30, dedup — `kb/phases/kb-m5-faza4-fallback-dedup.md`):**
> implementacja kodu z tej sesji (`app/fallback.py`, branch `task/kb-f4-fallback`, 3d4ee38)
> została **porzucona** — do mastera weszła równoległa, szersza implementacja tego samego
> kroku planu (e7625cd, `app/embed_router.py`, 2026-07-29) i to ona biega na PIHA. Ten log
@ -14,7 +23,7 @@
> Z delty brancha uratowano ponadto: `retrieval_eval.py --transport http` (plan §2 D6/§9)
> i luki testowe T1/T2 przeniesione do `test_embed_router.py`.
**Zakres**: `docs/kb/modules/05-faza4-plan.md` §2 decyzja 2 / §5 — aktywny fallback
**Zakres**: `kb/phases/kb-m5-faza4.md` §2 decyzja 2 / §5 — aktywny fallback
embedu, ostatni brakujący element rdzenia fazy 4 (frontend i ingress LIVE od
2026-07-22/23, `docs/sessions/2026-07-23-kb-f4-ingress.md`). Zero zmian w schemacie
DB, zero zmian w `kb_retrieval`'s retrieval logice — wyłącznie warstwa embed + health.

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-28
links: []
---
# Session log 2026-07-28
## Session 21:59
@ -10,8 +19,8 @@ e8aa3e3 docs(architecture): recon multiagent 2026-07-27
### Files changed
```
docs/architecture/PLAN-subsystem-a-2026-07-28.md | 60 +++
docs/architecture/RECON-multiagent-2026-07-27.md | 551 +++++++++++++++++++++++
kb/phases/subsystem-a-naprawa.md | 60 +++
kb/subsystems/recon-multiagent.md | 551 +++++++++++++++++++++++
2 files changed, 611 insertions(+)
```

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-07-30
links: []
---
# 2026-07-30 — HA: legacy zamknięte, MCP read-only (faza 2a)
## Legacy — finał

View file

@ -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
@ -13,7 +22,7 @@ pilota fazy 5 (narty27), wykonanego równolegle.
- session log 27.07
- testy luk T1T3
- komentarz kalibracji progów dist.
- Pełny rozbiór obu implementacji: `docs/kb/modules/05-fallback-dedup-raport.md`.
- Pełny rozbiór obu implementacji: `kb/phases/kb-m5-faza4-fallback-dedup.md`.
### Deploy na PIHA (z mastera)
- `ollama-piha`: named volume `ollama_piha_models`, model bge-m3, `KEEP_ALIVE=0`.
@ -67,7 +76,7 @@ infry**. Infra: `services/narty27`.
stubie nie przechodzi wyłącznie z tego powodu.
2. **`hosts/solaria/runtime/ollama/docker-compose.override.yml` — brak w repo**
(rozjazd repo↔runtime na SOLARII).
3. **R1R3 node-agent** (incydent `docs/incidents/2026-07-30-ollama-solaria-vanish.md`)
3. **R1R3 node-agent** (incydent `kb/incidents/2026-07-30-ollama-solaria-vanish.md`)
**niezrobione**: R1 `_prune_stopped_containers` nie może kasować kontenerów
zarządzanych, R2 rate-limit dla `ai_node`/`standard`, R3 logowanie usuniętych
zasobów. Przyczyna nadal aktywna → blokuje/warunkuje fazę mailową

View file

@ -0,0 +1,5 @@
# CHELSTY-INFRA
Runtime environment and deployment flow for CHELSTY, an offline-capable home automation edge node split across two VMs.
Dokumentacja: [kb/nodes/chelsty-infra.md](../../kb/nodes/chelsty-infra.md)

View file

@ -1,14 +1,5 @@
# PIHA - Infrastructure + Automation Node
# PIHA
## Role
- Core network services.
- Home automation (Home Assistant).
- Monitoring and logging.
Infrastructure + Automation Node.
## Configured Services
- Home Assistant
- Mosquitto (MQTT)
- Zigbee2MQTT
## Runtime Data
- `/opt/homelab/data/homeassistant`
Dokumentacja: [kb/nodes/piha.md](../../kb/nodes/piha.md)

View file

@ -1,6 +1,6 @@
# PIHA-specific override for node_exporter.
#
# WHY: KB module 5 phase 3 step 5 (docs/kb/modules/05-faza3-plan.md §7.2) needs the
# WHY: KB module 5 phase 3 step 5 (kb/phases/kb-m5-faza3.md §7.2) needs the
# textfile collector so kb-ingest's systemd timer can publish
# kb_ingest_last_success_timestamp / kb_ingest_last_exit_code / kb_ingest_embed_backlog
# etc. for fleet-prometheus to scrape and alert on.

View file

@ -84,7 +84,7 @@ services:
external: []
runtime:
# textfile collector reads /opt/homelab/state/node-exporter (module 5 phase 3 step 5,
# docs/kb/modules/05-faza3-plan.md §7.2 — kb-ingest.prom) via the existing /:/host:ro
# kb/phases/kb-m5-faza3.md §7.2 — kb-ingest.prom) via the existing /:/host:ro
# mount, see hosts/piha/runtime/node_exporter/docker-compose.override.yml.
data_path: /opt/homelab/state/node-exporter
@ -161,7 +161,7 @@ services:
runtime:
# No config and no secrets. Content is PERSONAL and deliberately outside
# the repo — it lives only in the Docker named volume
# narty27_narty27_content, refreshed from SOLARIA (services/narty27/README.md).
# narty27_narty27_content, refreshed from SOLARIA (kb/runbooks/narty27-deploy.md).
# No backup job, no /opt/homelab/data bind.
config_path: services/narty27

View file

@ -1,13 +1,5 @@
# SATURN - Primary Development & Orchestration Node
# SATURN
## Role
- Source of truth for all infrastructure Git repositories.
- Primary workstation for development and configuration management.
- The ONLY node allowed to commit changes to the homelab repositories.
Primary Development & Orchestration Node.
## Configured Services
(List services deployed on this host)
- Forgejo (Git source of truth)
## Runtime Data
- `/opt/homelab/data/forgejo`
Dokumentacja: [kb/nodes/saturn.md](../../kb/nodes/saturn.md)

View file

@ -1,11 +1,5 @@
# SOLARIA - Compute / GPU / Inference Node
# SOLARIA
## Role
- High-performance compute tasks.
- GPU-accelerated workloads (LLM inference, transcoding).
Compute / GPU / Inference Node.
## Configured Services
- Ollama
## Runtime Data
- `/opt/homelab/data/ollama`
Dokumentacja: [kb/nodes/solaria.md](../../kb/nodes/solaria.md)

View file

@ -1,7 +1,7 @@
# MITYGACJA TYMCZASOWA (M1) — założona 2026-08-04.
# NODE_TYPE=lte_node wyłącza run_safe_cleanup() (niefiltrowany
# `docker container prune`) na czas backfillu embed (faza mailowa KB).
# Incydent: docs/incidents/2026-07-30-ollama-solaria-vanish.md (§7, M1).
# Incydent: kb/incidents/2026-07-30-ollama-solaria-vanish.md (§7, M1).
# Bez tego każdy zatrzymany kontener na SOLARII znika w ≤60 s — również taki
# z `restart: unless-stopped`, zatrzymany świadomie przez operatora.
# Warunek zdjęcia: R1 (filtrowanie prune po restart policy / labelu compose)

View file

@ -1,13 +1,5 @@
# VPS - Public Edge + Ingress Node
# VPS
## Role
- Public-facing reverse proxy.
- HTTPS termination (Let's Encrypt).
- Edge security and routing.
Public Edge + Ingress Node.
## Configured Services
- Nginx Proxy Manager (NPM)
- Authelia / Authentik (Auth)
## Runtime Data
- `/opt/homelab/data/npm`
Dokumentacja: [kb/nodes/vps.md](../../kb/nodes/vps.md)

View file

@ -167,5 +167,5 @@ services:
# redis, mosquitto): legacy stack, runs UNMANAGED on vps and is scheduled
# for retirement — its codex/* bus has been idle since 2026-06-09.
# Deliberately NO entry here: legacy is not pulled into desired state.
# See docs/architecture/ai-cluster-LEGACY.md and
# docs/architecture/RECON-multiagent-2026-07-27.md (C9).
# See kb/decisions/ai-cluster-legacy.md and
# kb/subsystems/recon-multiagent.md (C9).

View file

@ -1,111 +0,0 @@
# deploy-runner — host-side executor for `redeploy` actions
Runs on each node that should be able to execute an operator-approved
`redeploy`. It is the missing half of the self-healing loop: the control-plane
executor can generate and dispatch redeploys, but nothing on the node could ever
perform one.
## What was broken
The executor ran `scripts/deploy/deploy-node.sh <node> <service>` **inside its own
container**. That script ignores both arguments, and expects a repo at
`${HOME}/homelab-codex-ws` — inside the container `HOME=/home/homelab`, so it
exited at line 18 with `Error: Repository not found`. Behind that failure sat
three more: no `git`, no `docker` CLI in the image, and — had it ever got that
far — it would have deployed the **executor host's** entire service set, not the
action's target node/service. Every `healthcheck_failed → redeploy` dead-ended
(recon `docs/architecture/RECON-multiagent-2026-07-27.md`, D14/D15).
## How it works now
```
supervisor → actions/pending/<id>.json (unchanged)
operator → actions/approved/<id>.json (unchanged, HITL)
executor (vps) → actions/deploy/<node>/<id>.json ← dispatch only, no execution
deploy-runner ← rsync-pull (node initiates; VPS never connects to a node)
→ scripts/deploy/deploy-service.sh --force-recreate
→ events/<node>/evt-…-action_result-….json → rsync-push
executor → completed / failed (via _reconcile_running_actions)
```
Design points, and why:
- **Host-level, not a container.** Compose resolves relative bind mounts and the
project name against the filesystem of whatever runs it. In a container those
resolve to container paths that the daemon then interprets as host paths — a
silent way to produce broken mounts, or to land in a different Compose project
where `--remove-orphans` deletes the running stack. On the host it behaves
exactly like a human `deploy.sh`.
- **Independent of node-agent** (own rsync pull, own result push) so it can
redeploy `node-agent` itself — the solaria case, where node-agent has been
docker-blind for weeks.
- **Separate inbox** from `actions/dispatch/<node>/`: node-agent deletes and
failure-reports every file in its own inbox that is not `container_restart`.
- **No `git pull`.** A redeploy reconciles the node to the checkout it already
has. Shipping new code stays a human `scripts/deploy/deploy.sh` action.
- **No `--build`, no `--remove-orphans`, always `--force-recreate`.** Plain
`up -d` is a no-op when config is unchanged — exactly the unhealthy-container
case; building on the 4 GiB VPS mid-incident is an OOM risk.
Every action is validated before anything runs (`action.py`): type must be
`redeploy`, `node` must match this node, the service name must be a plain
kebab-case name, the service must be listed in `hosts/<node>/services.yaml`, and
its compose file must exist. Nothing from the action payload is ever executed —
only a validated service *name* reaches the deploy script. Actions are
idempotent (marker in `state/processed-deploy-actions/`), single-instance
(`flock`), and time-boxed (`DEPLOY_TIMEOUT_SECS`, default 600 s, below the
executor's `REDEPLOY_TIMEOUT_SECS` of 900 s so the node reports before the
control plane gives up).
## Install (per node)
Not deployed by `deploy.sh` — it is a host-level systemd unit, like
`jobs/documents-ingest/`. On the target node:
```bash
# 1. config
sudo mkdir -p /opt/homelab/config/deploy-runner
sudo cp ~/homelab-codex-ws/jobs/deploy-runner/env.example \
/opt/homelab/config/deploy-runner/env
sudo chown oskar:oskar /opt/homelab/config/deploy-runner/env
sudoedit /opt/homelab/config/deploy-runner/env # set NODE_NAME; clear VPS_EVENTS_HOST on the VPS
# 2. units
sudo cp ~/homelab-codex-ws/jobs/deploy-runner/systemd/homelab-deploy-runner.{service,timer} \
/etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now homelab-deploy-runner.timer
# 3. verify (no action queued yet → one clean, empty run)
sudo systemctl start homelab-deploy-runner.service
journalctl -u homelab-deploy-runner.service -n 30 --no-pager
```
Requirements on the node: repo checkout at `REPO_PATH`, the service user in the
`docker` group, `python3` + PyYAML, `rsync`, and (remote nodes only) an ssh key
that reaches the VPS — the same one node-agent already uses for event shipping.
On the **VPS** leave `VPS_EVENTS_HOST` empty: the executor writes into the same
`/opt/homelab` mount, so there is nothing to pull and nothing to push.
## Operating it
- Dispatched but not yet collected: `ls /opt/homelab/actions/deploy/<node>/` on the VPS.
- Per-action deploy output: `/opt/homelab/logs/deploy-runner/<action_id>-<ts>.log` on the node.
- Runner activity: `journalctl -u homelab-deploy-runner.service`.
- A redeploy for a service with its own `deploy-local.sh` (control-plane) is
reported as **failed** with an explanatory message — those need an operator
deploy, by design.
- To let an already-processed action run again, remove its marker:
`rm /opt/homelab/state/processed-deploy-actions/<action_id>.done`.
## Tests
`tests/` — validation and rejection cases, the event format contract against the
executor's real parser, the `docker compose` argv contract of
`scripts/deploy/deploy-service.sh`, and an end-to-end run of the runner itself
with a stubbed `docker`.
```bash
python3 -m pytest jobs/deploy-runner/tests -q
```

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# jobs/deploy-runner/deploy-runner.sh — host-side executor for `redeploy` actions.
#
# WHY THIS EXISTS (docs/architecture/RECON-multiagent-2026-07-27.md D14/D15):
# WHY THIS EXISTS (kb/subsystems/recon-multiagent.md D14/D15):
# the control-plane executor could never carry out a redeploy — it ran
# deploy-node.sh inside its own container, a script that ignores its arguments
# and expects a repo at ${HOME}/homelab-codex-ws that does not exist there (nor
@ -9,7 +9,7 @@
# dead-ended, which is the single biggest reason the self-healing loop had
# 18 pending / 0 completed actions.
#
# The fix keeps the architecture decision from docs/backlog.md ("Remediacja
# The fix keeps the architecture decision from kb/phases/backlog.md ("Remediacja
# floty bez SSH"): the VPS never initiates a connection to a node. The executor
# only WRITES an action file; this runner, on the target node, pulls it, runs
# the deploy locally, and reports the outcome back through the existing event

View file

@ -1,6 +1,6 @@
# Eval-set for the retrieval quality gate (module 5, phase 3, plan §6.2,
# docs/kb/modules/05-faza3-plan.md). Transcribed 1:1 from the pilot baseline,
# docs/kb/eval/retrieval-pilot-2026-07-16.md (read-only source -- this file is the versioned
# kb/phases/kb-m5-faza3.md). Transcribed 1:1 from the pilot baseline,
# kb/phases/kb-m5-eval-retrieval-pilot.md (read-only source -- this file is the versioned
# copy the plan asked for, so the set stops living only in a session transcript).
#
# kind:
@ -80,7 +80,7 @@ queries:
wpis, patrz historia sesji). Próg tej kontroli obniżony do 0.50 (z 0.55) właśnie z powodu
tej znanej kolizji, żeby bramka nie płonęła co uruchomienie na nie-problemie.
# Faza mailowa (docs/kb/modules/05-faza-mailowa-plan.md, §8, Krok 5) -- bramka jakościowa dla
# Faza mailowa (kb/phases/kb-m5-faza-mailowa.md, §8, Krok 5) -- bramka jakościowa dla
# treści mailowej wprowadzonej w Etapie A (ostatnie 12 miesięcy, plan §7 Krok 4). Wypełniona
# przez operatora 2026-07-23 (5 zapytań "wiem że to mam w mailach z ostatniego roku"; M5
# odrzucone po weryfikacji, patrz N2 powyżej i historia sesji). expected_envelope celowo null

View file

@ -1,5 +1,5 @@
"""Retrieval quality gate -- module 5, phase 3, plan §6.2 (docs/kb/modules/05-faza3-plan.md),
extended in faza mailowa Krok 5 (docs/kb/modules/05-faza-mailowa-plan.md, §8) to add the
"""Retrieval quality gate -- module 5, phase 3, plan §6.2 (kb/phases/kb-m5-faza3.md),
extended in faza mailowa Krok 5 (kb/phases/kb-m5-faza-mailowa.md, §8) to add the
`hybrid` track once mail content exists in `document_chunk` (faza mailowa Krok 2/4).
Read-only integration script (NOT collected by pytest -- it hits the live kb-postgres DB and
@ -343,7 +343,7 @@ def print_report(
rows: list[dict], n_values: list[int], gate_result: dict, mail_rows: Optional[list[dict]] = None
) -> None:
print("=" * 100)
print("RETRIEVAL QUALITY GATE -- plan §6.2 (docs/kb/modules/05-faza3-plan.md), "
print("RETRIEVAL QUALITY GATE -- plan §6.2 (kb/phases/kb-m5-faza3.md), "
"+ hybrid/mail extension (05-faza-mailowa-plan.md §8)")
print("=" * 100)
header = f"{'id':<3} {'kind':<28} {'expected':<16} {'flat d1':>8} {'flat@3':>7}"

View file

@ -1,8 +1,8 @@
"""Chunk + embed job — module 5 phase 2, plan step 6 (docs/kb/modules/05-faza2-plan.md,
"""Chunk + embed job — module 5 phase 2, plan step 6 (kb/phases/kb-m5-faza2.md,
§6 step 6, decision 3).
`chunk_text`/`hard_split`/`split_paragraphs`/`TARGET_CHARS`/`OVERLAP_CHARS` moved to
`kb_mail.chunking` in module 5 faza mailowa, Krok 0 (docs/kb/modules/05-faza-mailowa-plan.md, §3)
`kb_mail.chunking` in module 5 faza mailowa, Krok 0 (kb/phases/kb-m5-faza-mailowa.md, §3)
so `jobs/mail-body-ingest` shares the exact same chunker instead of a copy-pasted drift; re-exported
here unchanged so nothing importing them from this module breaks.
@ -23,7 +23,7 @@ Install (from repo root):
pip install -e jobs/documents-ingest/
`embed_chunk`/`_vector_literal`/`DEFAULT_MODEL`/`DEFAULT_OLLAMA_URL` moved to
`kb_retrieval.embed` in module 5 phase 4 (docs/kb/modules/05-faza4-plan.md, §3, decision 1) so
`kb_retrieval.embed` in module 5 phase 4 (kb/phases/kb-m5-faza4.md, §3, decision 1) so
`kb-query` (Docker service) can share the same client without pulling in this job's `anthropic`
dependency; re-exported here unchanged so nothing importing them from this module breaks.

View file

@ -1,4 +1,4 @@
"""Cyclic ingest wrapper -- module 5 phase 3, plan step 5 (docs/kb/modules/05-faza3-plan.md,
"""Cyclic ingest wrapper -- module 5 phase 3, plan step 5 (kb/phases/kb-m5-faza3.md,
§7). Orchestrates one run of the recurring ingest pipeline for `kb-ingest.timer` on PIHA:
paperless_adapter.run() -- new source='paperless' envelopes

View file

@ -1,6 +1,6 @@
"""PDF attachment extractor — kb-postgres mail archive -> Paperless consume/.
Module 5 ("documents-ingest"), Phase 1 (docs/kb/modules/05-documents-ingest.md,
Module 5 ("documents-ingest"), Phase 1 (kb/phases/kb-m5-documents-ingest.md,
section "Domkniecie dlugu z maili"): pull a sample of PDF attachments out of the
Gmail .eml archive and drop them into Paperless' consume/ dir so Paperless does
the OCR + correspondent-detection. This is NOT the Paperless/Nextcloud envelope

View file

@ -1,4 +1,4 @@
"""Paperless -> envelope adapter — module 5 phase 2 (docs/kb/modules/05-faza2-plan.md,
"""Paperless -> envelope adapter — module 5 phase 2 (kb/phases/kb-m5-faza2.md,
§4.2-4.3, §6 step 5).
Reads documents from the Paperless REST API (read-only GET only, this job never

View file

@ -1,5 +1,5 @@
"""Re-export shim -- the retrieval module moved to `packages/kb-retrieval/` in module 5 phase
4 (docs/kb/modules/05-faza4-plan.md, §3, decision 1) so both this job and `kb-query` (Docker
4 (kb/phases/kb-m5-faza4.md, §3, decision 1) so both this job and `kb-query` (Docker
service) share one tested module. Kept here unchanged so nothing importing
`documents_ingest.retrieval` breaks; new code should import `kb_retrieval.retrieval` directly.
"""

View file

@ -1,4 +1,4 @@
"""Summarize + tag job — module 5 phase 3, plan step 3 (docs/kb/modules/05-faza3-plan.md,
"""Summarize + tag job — module 5 phase 3, plan step 3 (kb/phases/kb-m5-faza3.md,
§5 step 3, §2 decision 3: two-track A/B pilot).
Pipeline: `document_chunk.text WHERE excluded_reason IS NULL ORDER BY chunk_index` per

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# kb-ingest-run.sh — thin launcher for kb-ingest.service (module 5 phase 3 step 5,
# docs/kb/modules/05-faza3-plan.md §7.1).
# kb/phases/kb-m5-faza3.md §7.1).
#
# All sequencing/tolerance/exit-code logic lives in documents-ingest-cyclic (Python,
# jobs/documents-ingest/src/documents_ingest/cyclic_ingest.py) — this script only computes

View file

@ -49,8 +49,8 @@ class TestVectorLiteral:
class TestIsOcrJunk:
"""Pilot cases from docs/kb/modules/05-faza3-plan.md §3.1 and the 2026-07-16 calibration
session (docs/kb/eval/retrieval-pilot-2026-07-16.md)."""
"""Pilot cases from kb/phases/kb-m5-faza3.md §3.1 and the 2026-07-16 calibration
session (kb/phases/kb-m5-eval-retrieval-pilot.md)."""
def test_clean_text_is_not_junk(self):
text = (

View file

@ -1,4 +1,4 @@
"""Gmail header backfill — one-shot job, module 5 phase 2 (docs/kb/modules/05-faza2-plan.md, §5).
"""Gmail header backfill — one-shot job, module 5 phase 2 (kb/phases/kb-m5-faza2.md, §5).
Backfills `{"type": "headers", ...}` (§4.1) onto the 225 030 existing `source='gmail'`
envelope rows in kb-postgres, which today carry only an attachment manifest. This is an

View file

@ -1,5 +1,5 @@
"""Mail body ingest job — module 5, faza mailowa, plan Krok 2
(docs/kb/modules/05-faza-mailowa-plan.md, §5). Second full pass over the gmail .eml archive
(kb/phases/kb-m5-faza-mailowa.md, §5). Second full pass over the gmail .eml archive
(the first was gmail-bulk-import's manifest-only import): extracts inline body text that
`_parse_attachments` deliberately skipped, chunks it, embeds it (batched), and appends
`entities[type=threading]` the only new writes are `document_chunk` INSERTs and an additive

View file

@ -1,6 +1,16 @@
---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-07-30
as_of: 2026-07-30
links: []
---
# Recon — node-agent vs stability-agent (2026-07-30)
Read-only recon. Ground truth: `docs/architecture/RECON-multiagent-2026-07-27.md`
Read-only recon. Ground truth: `kb/subsystems/recon-multiagent.md`
(A1, A2, B7, D15). Source read at master @ `473bf8e`; this branch is cut from
master @ `0650eb8`. The two intervening merges (`0650eb8` ha-mcp, `cb8a19d`
kb-query tests) touch none of the audited paths — `services/node-agent/`,
@ -56,7 +66,7 @@ control plane, stability-agent feeds the agent-system UI, and stability-agent's
half of the event store is a write-only archive nothing has ever read.
A prior recon reached the same conclusion about the event path on 2026-07-06
(`docs/infra/prometheus-cutover-recon-2026-07-06.md:88-91`); it has not been acted on.
(`kb/audits/prometheus-cutover-2026-07-06.md:88-91`); it has not been acted on.
---
@ -297,7 +307,7 @@ prefix that `container_service_name()` was written to strip.
- Remove entries from `hosts/solaria/services.yaml` and `hosts/vps/services.yaml`.
- `docker compose down` on vps, piha, solaria (chelsty-infra when reachable).
- Purge or archive `/opt/homelab/events/2026-*/` on all four nodes (~5 MB on piha, mostly May).
- Update CLAUDE.md (agent-system architecture §1, event-path claim at line 100), `docs/chelsty-stability-agent.md`, recon A1/A2/B7.
- Update CLAUDE.md (agent-system architecture §1, event-path claim at line 100), `kb/services/chelsty-stability-agent.md`, recon A1/A2/B7.
- **Not covered by the cleanup:** the Redis publisher must be rehomed first or the UI loss is permanent.
### (b) Merge stability-agent's unique checks into node-agent

View file

@ -1,3 +1,13 @@
---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-07-23
as_of: 2026-07-23
links: []
---
# Audyt spójności automatyzacji instancji `ken` — 2026-07-23
Zakres: wyłącznie analiza; żadnych zmian w automatyzacjach/skryptach/scenach.

View file

@ -1,3 +1,13 @@
---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-07-16
as_of: 2026-07-16
links: []
---
# Recon: lustro `event=dead prom=up` — 1507 mismatchy w shadow-liveness.log (2026-07-16)
READ-ONLY recon. Zero zmian w kodzie/serwisach. Wszystkie czasy **UTC**

View file

@ -1,3 +1,13 @@
---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-07-14
as_of: 2026-07-14
links: []
---
# Monitoring coverage — co biega vs co jest monitorowane (recon 2026-07-14)
**Pytanie:** czy wszystkie serwisy floty są monitorowane?
@ -121,7 +131,7 @@ compose-service kontenera.
| owntracks-prometheus-exporter-prometheus-owntracks-exporter-1 | linusgroh/prometheus-owntracks-exporter | Up 2w | 0.0.0.0:8780→80 |
| own-tracks-frontend-owntracks-frontend-1 | owntracks/frontend | Up 2w | 0.0.0.0:8084→80 |
Zmiany vs audyt 2026-06-30 (`docs/infra/inventory-2026-06-30.md`): **przybyły** paperless,
Zmiany vs audyt 2026-06-30 (`kb/subsystems/fleet-inventory.md`): **przybyły** paperless,
paperless-db, paperless-broker (Deploy 1, 2026-07-10); **zniknęły** diskover i elasticsearch
(w audycie 06-30 były w 33 shadow; dziś nie biegają). 06-30: 40 kontenerów → dziś: 42.

View file

@ -1,6 +1,16 @@
---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-07-02
as_of: 2026-07-02
links: []
---
# Audyt odchudzania PIHA — 2026-07-02
> Faza 1 (READ-ONLY) modulu 0 filaru dokumentow (`docs/kb/modules/00-piha-slim.md`).
> Faza 1 (READ-ONLY) modulu 0 filaru dokumentow (`kb/phases/kb-m0-piha-slim.md`).
> Zadna akcja nie zostala wykonana — wylacznie `docker stats/inspect/logs`, `ss`, `curl` (odczyt).
> Stan w momencie audytu: **RAM 7.9Gi total, 5.0Gi used, 2.9Gi available; swap 4Gi total, 2.0Gi uzyty.**
> 41 kontenerow Up (inwentaryzacja 2026-06-30 liczyla 40; wszystkie nadal biega).

View file

@ -1,3 +1,13 @@
---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-07-06
as_of: 2026-07-06
links: []
---
# Prometheus liveness cutover — recon starego toru (2026-07-06)
Read-only recon przed cutoverem liveności floty na Prometheus `up{}`. Mapa obecnego
@ -290,7 +300,7 @@ renderuje `health`/`status`/`last_seen`), `/unhealthy` (`:302-334`), `/summary`,
chelsty/chelsty-infra (exporter DOWN po LTE, 2026-06-26).
- Reguła `NodeDown` (`rules/liveness.yml:21-28`): `up{node=~"vps|piha"} == 0`,
`for: 5m`, severity critical. solaria/lustro świadomie wykluczone (`:12-16` —
planned power-off; docelowo anomaly detection, `docs/backlog.md:390-406`).
planned power-off; docelowo anomaly detection, `kb/phases/backlog.md:390-406`).
Bez Alertmanagera by design (`:3-7`) — delivery = brain-watchdog poll `/api/v1/alerts`.
- node_exporter w repo tylko dla VPS (`services/node_exporter/`, network_mode: host,
`hosts/vps/services.yaml:36-43`). Exportery na piha/solaria/lustro **nie mają
@ -338,7 +348,7 @@ chelsty-infra, chelsty-ha, lustro.
| lustro | TAK (`:50-52`) | NIE | TAK (bez stability-agenta) | jw. |
| **chelsty-infra** | **NIE** (`:57-60`, exporter DOWN po LTE) | NIE | **TAK** (remote TTL 900/3600, `liveness.py:43-50`) | **tylko stary tor — cutover totalny zostawiłby go bez liveności** |
| chelsty-ha | NIE | NIE | NIE (`hosts/chelsty-ha/services.yaml:6-12`, `monitor: false`) | już dziś bez liveności (pośrednio przez MQTT chelsty-infra) — cutover nic nie zmienia |
| saturn | NIE (`:55`, laptop) | NIE | NIE (brak `hosts/saturn/services.yaml`, `docs/backlog.md:423`) | już dziś bez liveności — cutover nic nie zmienia |
| saturn | NIE (`:55`, laptop) | NIE | NIE (brak `hosts/saturn/services.yaml`, `kb/phases/backlog.md:423`) | już dziś bez liveności — cutover nic nie zmienia |
**Chelsty offline ~34 dni — jak traktuje go stara rura:** eventy buforują się lokalnie
(rsync fail = non-fatal, `node_agent.py:560-566`), `last_seen` na VPS zamrożone sprzed
@ -351,7 +361,7 @@ totalnym chelsty-infra nie miałby żadnej liveności i żadnego przejścia offl
Dodatkowo docs sygnalizują konflikt IP w komentarzach `prometheus.yml:57` vs
`hosts/chelsty-infra/host.yaml:12` — do wyjaśnienia przy ewentualnym dodawaniu scrape.
*(rzeczywisty bieżący stan chelsty — do weryfikacji na żywo; ostatni zapis:
UNREACHABLE, `docs/infra/inventory-verify-2026-07-02.md:17,151`)*
UNREACHABLE, `kb/subsystems/fleet-inventory-verify.md:17,151`)*
**Wniosek twardy:** cutover NIE może być globalny. Docelowa architektura to
**hybryda per-node**: `up{}` dla scrape'owanych (vps, piha, solaria, lustro),
@ -450,7 +460,7 @@ z `last_seen` — rzadszy heartbeat przy niezmienionych TTL-ach = fałszywe degr
- chelsty-infra: zbadać exporter-over-LTE (`prometheus.yml:57-60` + konflikt IP
z `hosts/chelsty-infra/host.yaml:12`); do tego czasu zostaje na torze eventowym.
- NodeDown dla solaria/lustro: świadomie odroczone do anomaly detection
(`docs/backlog.md:390-406`) — nie wciągać do cutoveru.
(`kb/phases/backlog.md:390-406`) — nie wciągać do cutoveru.
- Watchdog na sam Prometheus (D.2 pkt 5) — mały task przy etapie 3.
- saturn / chelsty-ha: świadomie poza monitoringiem — status quo.
@ -458,7 +468,7 @@ z `last_seen` — rzadszy heartbeat przy niezmienionych TTL-ach = fałszywe degr
**Seria.** Minimalnie trzy taski implementacyjne + weryfikacje między nimi:
(1) etap 1 shadow-read; (2) etap 3 flaga per-node (po tygodniu etapu 2);
(3) watchdog-na-Prometheusa + aktualizacja `docs/observer-runtime.md`.
(3) watchdog-na-Prometheusa + aktualizacja `kb/subsystems/observer.md`.
Etap 0 to czynność operatorska (runtime, nie repo). Etap 5 to niezależny backlog.
---

View file

@ -1,3 +1,13 @@
---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-07-27
as_of: 2026-07-27
links: []
---
# Audyt niezarządzanych stacków na VPS — 2026-07-27
Recon read-only przed konsolidacją do GitOps. Zebrane przez `ssh vps` (user `oskar`,

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: decision
visibility: public
status: active
updated: 2026-07-30
links: []
---
# ai-cluster — LEGACY, wygaszany (decyzja 2026-07-28)
## Decyzja
@ -5,7 +14,7 @@
Stack **ai-cluster** działający na vps (`ai-cluster-openclaw-1`, `codex-worker`,
`planner-worker`, `service-ops-worker`, `redis`, `mosquitto`) jest **wygaszany,
nie migrowany**. Podstawa (recon
[RECON-multiagent-2026-07-27.md](RECON-multiagent-2026-07-27.md), C9):
[RECON-multiagent-2026-07-27.md](../subsystems/recon-multiagent.md), C9):
bus `codex/*` jest martwy od **2026-06-09** — zero nowych połączeń przez ~7 tygodni,
workery trzymają tylko puste długożyjące połączenia.

View file

@ -1,8 +1,17 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-07-29
links: []
---
# Architektura — decyzje obowiązujące
Stan decyzji na 2026-07-28. Podstawa dowodowa:
[RECON-multiagent-2026-07-27.md](RECON-multiagent-2026-07-27.md); plan wykonawczy:
[PLAN-subsystem-a-2026-07-28.md](PLAN-subsystem-a-2026-07-28.md). Zmiana którejkolwiek
[RECON-multiagent-2026-07-27.md](../subsystems/recon-multiagent.md); plan wykonawczy:
[PLAN-subsystem-a-2026-07-28.md](../phases/subsystem-a-naprawa.md). Zmiana którejkolwiek
decyzji wymaga aktualizacji tego pliku z nową datą.
## Dwa subsystemy (2026-07-28)
@ -33,7 +42,7 @@ Stack ai-cluster na vps (openclaw, codex-worker, planner-worker, service-ops-wor
redis, mosquitto) jest **wygaszany, nie migrowany**. Bus `codex/*` martwy od
2026-06-09 (zero nowych połączeń). Branch `task/ai-cluster-solaria` zostaje
**niezmergowany** — pełni rolę dokumentacji. Kontenery na vps zostaną zatrzymane w
osobnej, nadzorowanej sesji. Szczegóły: [ai-cluster-LEGACY.md](ai-cluster-LEGACY.md).
osobnej, nadzorowanej sesji. Szczegóły: [ai-cluster-LEGACY.md](ai-cluster-legacy.md).
## Approvale zostają HITL (2026-07-28)

View file

@ -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 `kb/runbooks/narty27-deploy.md` — podczas gdy `narty27.kapala.org` jest **publiczne**
(NPM na VPS + cert Let's Encrypt). Pole `exposure` steruje traktowaniem ekspozycji
przez agentów (patrz „Discovery Entry Points for Agents" w CLAUDE.md — `service.yaml`
jest kontraktem operacyjnym, z którego agent czyta, jak zarządzać serwisem), więc
rozjazd kontrakt↔rzeczywistość jest tu groźniejszy niż zwykła nieaktualna
dokumentacja: agent podejmie decyzję na podstawie pola, które kłamie.
**Fix (osobny task)**: (1) **najpierw** zweryfikować, co realnie konsumuje pole
`exposure` (observer / supervisor / ścieżka deployu) — dopiero to pokaże, czy poza
dokumentacją zmiana coś przestawia; (2) potem poprawić `service.yaml` + README na
`public`, z komentarzem wskazującym NPM VPS host **#14** i cert LE **#38**.
---
### `scripts/ha/deploy.sh --delete`: brak wspólnego toru kasacji automatyzacji
**Data**: 2026-07-27
**Źródło**: sesja porządków po audycie (`task/ha-porzadki`); kontekst bezpośredni:
kasacja pkt 10 (`36b43e5`, para Tymka/powitanie-test/notify-router/para-prototyp)
zrobiona pętlą ręcznych `curl DELETE` po API zamiast przez repo tooling.
**Problem**: `deploy.sh` ma tylko WRITE (`POST /api/config/<domain>/config/<id>`) —
zgodnie z DESIGN.md ("Deploy path") i istniejącym wpisem w tym backlogu (sekcja
"Cutover HA ken", krok 4) DELETE obiektów usuniętych z repo jest poza zakresem,
drift-check tylko ostrzega (`warnings`), nigdy nie kasuje. Efekt: jedyna droga
usunięcia automatyzacji z żywej instancji to ręczne wywołanie API, bez
drift-check/`check_config`/verify, czyli bez żadnej z gwarancji, które deploy.sh
daje dla write.
**Fix**: `deploy.sh <instance> --delete <plik...>` (albo `--delete` jako tryb pracy
na plikach usuniętych z repo, wykrytych przez `drift_warnings`) z tym samym rytmem
co write: dry-run domyślny, `--dry-run`/LIVE jak dziś, `DELETE
/api/config/<domain>/config/<id>` per obiekt, verify (GET → oczekiwane 404) zamiast
porównania treści. Scope jak przy write: automations/scripts/scenes.
---
### HA ken: `input_number.klima_salon_tolerancja` nie ma triggera — zmiana nie przelicza progu
**Data**: 2026-07-27
**Źródło**: sesja porządków po audycie (`task/ha-porzadki`), przy okazji przeglądu
`1784804667795` dla konwencji automatyzacji (pkt 17)
**Problem**: `"Klima salon: włącz chłodzenie i synchronizuj cel"` (`1784804667795`)
ma trigger `id: sync` na `input_number.klima_salon_temp_docelowa` (zmiana celu od
razu przelicza próg), ale brak odpowiednika dla `input_number.klima_salon_tolerancja`
— to ta sama klasa buga co "trigger brzegowy bez lustrzanego warunku" z audytu
(sekcja 3, wzorzec `klima_salon` z fixu `faa2e2a`), tylko po stronie triggerów, nie
warunków: zmiana tolerancji na dashboardzie nic nie przelicza do najbliższej
naturalnej zmiany `sensor.thsalon_temperature`.
**Fix**: dodać trigger `state` na `input_number.klima_salon_tolerancja` do gałęzi
`sync` (albo do analogicznej gałęzi w automatyzacji OFF, jeśli tolerancja wpływa też
na próg wyłączenia) w `1784804667795`, tak samo jak istniejący trigger na
`_temp_docelowa`.
---
### HA ken: guard TRV kalibracji przed sezonem grzewczym (~2026-09)
**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcja 1.4
(pkt 2 checklisty operatora)
**Problem**: kalibracje TRV sypialnia (`1764751049013`) i Tymek (`1765817937658`) liczą
`room_temp` jako średnią z czujnika zhimi, który jest `unavailable` od 2026-07-17 →
`float(0)` w formule zaniża temperaturę o połowę → kalibracja dojechała do 5.0
(potwierdzone w fixtures). Latem (TRV `off`) nieszkodliwe; w sezonie grzewczym =
trwałe przegrzewanie obu pokoi. Dodatkowo wszystkie 6 automatyzacji TRV pollują co
4050s (baterie 2038%), a SalonPrawy ma clamp `diff` ±5 zamiast ±1.5 jak reszta.
**Fix (przed sezonem)**: (1) guard `is not unavailable` na czujnikach zhimi zamiast
ślepego uśredniania; (2) ręczny reset kalibracji sypialnia/Tymek po naprawie; (3)
zwolnić pętle do ≥5 min lub trigger na zmianę stanu; (4) ujednolicić clamp SalonPrawy;
(5) reanimować albo wyłączyć TRV łazienki (`number.*_calibration` unavailable —
urządzenie zniknęło z sieci).
---
### HA ken: przycisk graceful shutdown klimy salonowej na kartę dashboardu
**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcja 3.1,
fix-pack 1 (`task/ha-fix-pack-1`, DESIGN.md „Decyzje operatora po audycie 2026-07-23")
**Kontekst**: po fix-packu 1 „Klima salon: wyłącz…" (`1784804668795`) respektuje
`klima_salon_auto = on` dla gałęzi sunset/balkon; suszenie parownika przy ręcznym
zgaszeniu `klima_salon_auto` działa już tylko jako efekt uboczny przełącznika trybu
auto (osobna gałąź `choose` na trigger `auto_off`). Brakuje wygodnego jednego
przycisku „wyłącz klimę i osusz teraz" niezależnego od przełącznika auto.
**Fix**: dodać na dashboard przycisk/skrypt wywołujący `script.klima_salon_dry_off`
bezpośrednio (bez przełączania `klima_salon_auto`), żeby ręczne graceful shutdown nie
wymagało znajomości wewnętrznej logiki automatyzacji.
---
### HA ken: diagnoza wspólnej awarii sprzętowej 2026-07-17 (czujniki ruchu, pilot 4button, xiaomi_miot)
**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcje 1.2,
1.6, 4.5 (pkt 1, 3, 15 checklisty operatora)
**Problem**: restart HA / update Supervisora 2026-07-17 15:07 zbiega się z
`unavailable` na: klaster czujników ruchu/obecności (mdwejscie, mdsypialnia, mdheli —
przynajmniej od restartu; occusalon/mdtymka/mdubikacja gasną w kolejnych dniach —
obraz siadających baterii), całej integracji xiaomi_miot (fan.zhimi_mb3/mc2, czujniki
temperatury zhimi używane w kalibracjach TRV) i `sensor.4button_battery` (mimo że pilot
4button strzelał jeszcze 2026-07-12 — 8 automatyzacji salonu na tym urządzeniu). Nie
jest jasne, czy to jedna awaria (Zigbee coordinator/integracja) czy zbieg kilku.
**Fix**: (1) sprawdzić fizycznie baterie/re-pairing 6 czujników ruchu + czujnik
wycieku WC (pkt 1 checklisty); (2) zdiagnozować integrację xiaomi_miot po stronie HA
zamiast łatać pojedyncze automatyzacje (pkt 3); (3) sprawdzić fizycznie baterię pilota
4button (pkt 15). Reanimacja czujników ruchu odblokuje też ~15 cicho martwych
automatyzacji (alerty on-leave, nocne gaszenie, `Poranek start`) — patrz audyt sekcja
1.2 dla pełnej listy skutków.
---
### HA ken: projekt „architektura night_mode" (konsolidacja sleep/night mode)
**Data**: 2026-07-23
**Źródło**: `kb/audits/ha-automatyzacje-2026-07-23.md` sekcje 2.2,
2.4, 6 (pkt 7, 9 checklisty operatora — świadomie NIE załatane w fix-packu 1, patrz
DESIGN.md „Decyzje operatora po audycie 2026-07-23")
**Problem**: cztery flagi trybu (`sleep_mode`, `night_mode`, `passive_mode`,
`movie_mode`) o częściowo pokrywającej się semantyce, sprawdzane niespójnie (raz jedna
flaga, raz druga, raz OR obu); `automation.turn_off_sleep_mode_at_sunrise` steruje w
rzeczywistości `night_mode`. Cztery nakładające się automatyzacje gaszą ten sam zestaw
świateł nocą (`1702844937214` martwa, `1763384250145` martwa, `1768946230585` enforcer
co ~10 min całą noc, `1700832676138` o 3:00) — po reanimacji martwych czujników (patrz
wpis wyżej) trzy z nich ożyją naraz i zaczną się ścigać. `1768946230585` ma dodatkowo
trigger `time_pattern /5` obok krawędziowego `off→on`, więc mimo aliasu „5 minut po
aktywacji" gasi światła cyklicznie całą noc — do decyzji, czy to zamierzone.
**Fix (projekt, nie one-liner)**: skonsolidować do jednego `input_select.tryb_domu` +
jednej automatyzacji „nocne domknięcie" z jasnym priorytetem trybów i enforcerem
(cykliczny dozorca vs. jednorazowe zadziałanie po aktywacji — decyzja pkt 7), plus
finalny/ostateczny wyłącznik nocny zastępujący dzisiejsze cztery. Zakres większy niż
fix-pack — osobny task.
---
### Executor: zepsuty JSON w `approved/` retry'owany w nieskończoność co 10s zamiast trafić do `failed/`/`rejected/`
**Data**: 2026-07-23
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: podczas testów E2E remediacji bez SSH zepsuty JSON akcji w `approved/`
(przyczyna: podstawienie zmiennej lokalnej w cudzysłowie w zagnieżdżonym heredoc przez
ssh) powodował, że executor próbował go sparsować co cykl (10s), logując "Failed to
move ... to running: Expecting value" bez końca — plik nigdy nie trafiał do
`failed/`/`rejected/`.
**Fix**: executor powinien przenosić nieparsowalny JSON do `failed/` (albo dedykowanego
`malformed/`) po pierwszej próbie, nie zostawiać go do nieskończonego retry w `approved/`.
---
### Uprawnienia `actions/` naprawione TYLKO na PIHA (ręcznie) — SOLARIA i lustro niezweryfikowane
**Data**: 2026-07-23
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: fix uprawnień `/opt/homelab/actions` (grupa `pi` + setgid, patrz Zamknięte)
zastosowany ręcznie tylko na PIHA. SOLARIA (oskar tam podobno uid 1000 — do sprawdzenia
czy problem w ogóle występuje) i lustro (repo-less, patrz osobny wpis niżej)
niezweryfikowane — remediacja na tych węzłach może paść identycznie przy pierwszej
próbie.
**Fix docelowy**: node-agent powinien sam tworzyć `dispatch/<node>/` z właściwymi
prawami (grupa współdzielona + setgid) przy starcie, żeby to nie wracało za każdym
razem jako ręczna interwencja — patrz też „Tech-debt: globalny porządek uid/gid" niżej.
---
### `deploy-local.sh` (control-plane): brak twardego checka, że `.env` istnieje i `TAILSCALE_BIND_IP` jest ustawione
**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: przy fixie bindu `operator_ui.py:18180` odkryto footgun: bez `.env`,
`docker compose` tylko OSTRZEGA i po cichu wraca do bindu `0.0.0.0` zamiast failować —
dokładnie ten sam wzorzec błędu jak historyczny bug fleet-prometheus/deploy-node.sh
(brak `--env-file`, patrz Zamknięte, commit `686aca7`), tym razem w
`deploy-local.sh`/control-plane.
**Fix**: dodać twardy check w `deploy-local.sh``.env` musi istnieć i
`TAILSCALE_BIND_IP` musi być ustawiony, inaczej abort przed `docker compose up`.
---
### `operator_ui.py` nie ma ŻADNEJ autoryzacji
**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: po zamknięciu publicznego bindu 18180 (patrz Zamknięte, commit `9a5c160`)
`operator_ui.py` nadal przyjmuje `/action/mutate` (w tym przejście do `approved`) bez
żadnego uwierzytelnienia — chroni tylko granica Tailscale mesh, nie autoryzacja per
operator.
**Fix**: osobny temat — do zaprojektowania (token/basic auth/mTLS w mesh), poza
zakresem fixu bindu.
---
### `alert_only` zapycha approval queue
**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: przy reconie stanu wyjściowego 16 z 18 pending actions to były
liveness-transitions typu `alert_only`, nie realne decyzje do klikania — operator musi
przewijać szum, żeby znaleźć akcje, które faktycznie wymagają Approve/Reject.
**Fix**: rozważyć osobny widok/filtr dla `alert_only` w operator UI, albo
auto-acknowledge bez wejścia do tej samej kolejki co `container_restart`/`redeploy`.
---
### Crash-loop nie generuje `container_restart` — gap detekcja→akcja
**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: `node_exporter` na PIHA miał 1048 restartów i event `[high]`, ale
supervisor nie wygenerował żadnej akcji `container_restart` — crash-loop widoczny w
evencie nie przekłada się na akcję remediacyjną.
**Fix**: zbadać routing supervisora dla crash-loop sygnałów (`disk_pressure` i
`containers_not_running` mają jasne mapowanie na akcje w CLAUDE.md — crash-loop
najwyraźniej nie).
---
### Telegram yes/no dla pending actions brakuje
**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: brain-watchdog ma `send_telegram` (używane dla alertów Prometheus), ale
nic nie łączy go z `actions/pending` — operator musi wejść do operator UI, żeby
zatwierdzić/odrzucić akcję, zamiast dostać yes/no bezpośrednio na Telegramie.
**Fix**: rozszerzyć brain-watchdog (albo osobny konsument) o powiadomienie z
przyciskami approve/reject per pending action.
---
### `homeassistant5` na PIHA: `Exited (0)`, nie podnosi się mimo `restart: unless-stopped`
**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: kontener `homeassistant5` (legacy HA "ken", patrz cutover
`kb/phases/backlog.md` sekcja "Cutover HA ken") zaobserwowany w stanie `Exited (0)` — exit
code 0 = czyste zatrzymanie, więc Docker `restart: unless-stopped` świadomie go nie
podnosi (to nie crash). Do zweryfikowania, czy to zamierzone wygaszenie z cutoveru czy
coś zatrzymało kontener niezamierzenie.
**Fix**: sprawdzić czy to spójne z planem wygaszenia `homeassistant5` (patrz cutover
HA ken, krok 2 „Wygaszenie homeassistant5") — jeśli tak, brak akcji; jeśli nie,
zbadać czemu wyszedł z kodem 0.
---
### lustro: `node_agent.py` wymaga ręcznego wypchnięcia do `/opt/homelab/deploy/node-agent` (repo-less)
**Data**: 2026-07-22
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
**Problem**: węzeł lustro nie ma repo/gita — `node_agent.py` (z fixami remediacji bez
SSH, commit `2dac154`) musi być ręcznie skopiowany do
`/opt/homelab/deploy/node-agent`, żeby dotrzeć na ten węzeł. Ryzyko rozjazdu
repo↔runtime identyczne jak przy innych repo-less węzłach.
**Fix**: ustalić mechanizm dystrybucji kodu node-agenta na węzły repo-less (rsync przy
deployu / paczka artefaktu / inny kanał) zamiast ręcznego kopiowania.
---
### 🔴 npm@VPS panel admina :81 publicznie osiągalny z internetu
**Data**: 2026-07-10
**Źródło**: sesja `scripts/npm/npm_api.py` (skrypt do zarządzania NPM przez REST API)
**Problem**: `services/npm/docker-compose.yml` mapuje `81:81` bez ograniczenia do
interfejsu — Docker bindem domyślnym wystawia to na `0.0.0.0`, czyli panel admina
NPM@VPS jest osiągalny z publicznego IP (`135.181.153.108:81`), nie tylko przez
Tailscale mesh (`100.95.58.48:81`). Panel admina (login+hasło, bez 2FA wymuszonego)
nie powinien być publiczny. Brak `hosts/vps/runtime/npm/docker-compose.override.yml`
ograniczającego bind.
**Fix**: dodać override z bindem `127.0.0.1:81:81` (dostęp tylko przez Tailscale/SSH
tunnel) albo `<tailscale_ip>:81:81`, zachowując `80`/`443` publiczne (to jest ich rola).
Zweryfikować po zmianie, że `npm_api.py --npm vps` nadal łączy się przez
`100.95.58.48:81`.
---
### Ghosty B WRÓCIŁY — hash-prefixed control-plane na VPS nieposprzątane
**Data**: 2026-07-06
**Źródło**: sesja 2026-07-06 (`docs/sessions/2026-07-06.md`) — snapshot panelu agents.okit.pl
**Problem**: hash-prefixed kontenery control-plane znów widoczne na VPS —
wpis „ZNIKNĘŁY (potwierdzone reconem 2026-07-02)" w Zamkniętych zdezaktualizowany.
**Fix**: ręczny `docker rm` hash-prefixed kontenerów control-plane na VPS;
przy okazji sprawdzić, skąd wróciły (fix A `3b71707` miał blokować źródło divergence).
**Update 2026-07-16** (sesja `docs/sessions/2026-07-16.md`): po odetkaniu supervisora
(event flood + pętla zamrożona, oba naprawione dziś) potwierdzone, że wpisy `error`
w topologii panelu to dokładnie te ghosty — hash-prefixed w world_state observera,
NIE żywe kontenery (`docker ps -a exited=0` na VPS). Observer nie prune'uje wpisów
po zniknięciu kontenerów spod tych nazw. To trzyma `System Status: ERROR` fałszywie.
**Fix (do zrobienia)**: observer powinien weryfikować faktyczny stan kontenera przy
budowaniu world_state i prune'ować wpisy dla kontenerów, których już nie ma (ta sama
klasa błędu co „Rozjazd world-state observera: NOMINAL przed istnieniem" niżej).
---
### shadow_mode → remediacja: decyzja o auto-restart
**Data**: 2026-07-16
**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane
**Problem**: po odetkaniu supervisora (event flood + pętla zamrożona, oba naprawione
dziś) kolejka akcji nadal pusta częściowo dlatego, że `shadow_mode=True` downgrade'uje
HA `container_restart` do `alert_only` — supervisor widzi problem, ale świadomie nie
enqueue'uje akcji restartu.
**Do decyzji**: czy i kiedy włączyć auto-restart padłych kontenerów — wymaga
architektury (guardraile, cooldowny, blast radius per serwis) zanim `shadow_mode=false`.
Patrz też istniejący wpis „ha-diag-agent deploy ZABLOKOWANY" niżej — przed
`shadow_mode=false` tam wymieniony konkretny target (`homeassistant5`).
---
### gokapi: deploy-node VPS rzuca błąd — brakujący `.env`
**Data**: 2026-07-16
**Źródło**: sesja 2026-07-16 (`docs/sessions/2026-07-16.md`) — wątek control-plane
**Problem**: `deploy-node.sh` na VPS rzuca błąd na serwisie gokapi z powodu brakującego
`.env` (`/opt/homelab/config/gokapi/.env` nieutworzony lub niepełny — wzorzec z sesji
2026-07-09 `docs/sessions/2026-07-09-kb-configi-gokapi.md`).
**Fix**: sprawdzić `services/gokapi/env.example`, utworzyć/uzupełnić `.env` na VPS wg
konwencji `env.example``/opt/homelab/config/<service>/.env`.
---
### elasticsearch + diskover w stanie error na PIHA — observer zna usunięte serwisy
**Data**: 2026-07-06
**Źródło**: sesja 2026-07-06 (`docs/sessions/2026-07-06.md`) — snapshot panelu agents.okit.pl
**Problem**: elasticsearch i diskover usunięte z PIHA w module 0 (2026-07-02),
ale observer wciąż je zna i raportuje `error` w panelu. World-state nie zapomina
serwisów, które przestały istnieć — ta sama klasa błędu co „NOMINAL przed
istnieniem" (patrz wpis z 2026-06-25).
**Fix**: wyczyścić martwe wpisy z world_state / dodać wygaszanie serwisów
nieobecnych w desired state i w dockerze.
---
### Gotchas (z 2026-06-30 — migracja kapala.org → Cloudflare/wildcard)
**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
- **NPM custom WS config + Websockets Support:** NIE wklejać
`proxy_http_version 1.1;` do Advanced gdy Websockets Support = ON.
NPM dodaje tę dyrektywę sam → duplikat → nginx -t failuje → plik
proxy_host/<id>.conf się NIE generuje → "unrecognized name" mimo
dobrego certu. Objaw mylący (wygląda jak problem certu/DNS).
Diagnoza: `strings /data/database.sqlite | grep <domena>` → pole nginx_err.
- **Cloudflare auto-proxy na import:** CF proxuje A/CNAME przy dodaniu strefy.
DKIM CNAME (fm1/2/3._domainkey) proxied = zepsuty podpis maila.
Zawsze przełączyć na DNS only (szara chmurka) przed aktywacją.
Reserved/CGNAT IP (Tailscale 100.x) CF wymusza DNS only automatycznie.
---
### Migracja okit.pl → Cloudflare (większy projekt, firmowa domena)
**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
Naprawi wszystkie wygasłe certy okit.pl naraz (HTTP-01 failuje przy mesh DNS):
ap, audiobooks, code-server, dysk, forgejo, ha-embed, hagc, ngpm, node-red,
okit.pl, pihole, ha.okit.pl. Wzorzec jak kapala.org: NS na CF, wildcard *.okit.pl
przez DNS-01, przepiąć hosty. UWAGA: okit.pl ma usługi publiczne (foty) —
rozdzielić mesh-only od publicznych. Ostrożnie — firmowa domena.
---
### foty.kapala.org renew failuje (#47, expired 2026-06-19, HTTP-01)
**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
Foty są PUBLICZNE celowo (zewn. dostęp za hasłem NPM) — port 80 powinien
być dostępny, więc HTTP-01 powinno działać. Sprawdzić czemu failuje
(DNS foty wskazuje na zły IP? port 80 zablokowany?). NIE przenosić na mesh.
---
### Cleanup po błędnej ścieżce HA-Tailscale-addon (z 2026-06-29)
**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
- ha-ken: Stop + Uninstall add-on Tailscale
- Tailscale admin: usunąć node ha-ken (100.98.128.40)
- 42.pl/okit.pl: usunąć rekord ha-ken → 87.205.110.38 (jeśli jest)
---
### Stary ha.okit.pl (cert wygasł 6/28, teraz "Not Used")
**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
Zostawić lub usunąć proxy host + DNS. Niepilne (martwy, nie szkodzi).
---
### Stopniowa migracja usług domowych okit.pl → kapala.org (mesh-only)
**Data**: 2026-06-30
**Źródło**: sesja 2026-06-30 (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`)
dysk, audiobooks, budget, code-server, home, ha-embed, node-red... wg wzorca
migracji usługi na kapala.org (patrz sesja: CF rekord A → 100.108.208.3 DNS only,
NPM proxy host z wildcard *.kapala.org, Advanced PUSTE, weryfikacja grep+curl).
---
### `deploy-node.sh` nie reloaduje config-driven serwisów (cicha rozbieżność deploy↔config)
**Data**: 2026-06-26
**Źródło**: sesja 2026-06-26 (`docs/sessions/2026-06-26.md`)
**Problem**: po zmianie `prometheus.yml` (bez zmiany obrazu) `deploy-node.sh` NIE
recreate'uje kontenera. Compose widzi "kontener działa, obraz ten sam" → zostawia
Running, NIE podmienia configu → Prometheus trzyma stary config w pamięci. **Deploy
raportuje green, a zmiana configu nie wchodzi w życie.** Dziś wymagało ręcznego
`docker compose ... up -d --force-recreate`. Dotyczy KAŻDEGO serwisu config-driven bez
zmiany obrazu (nie tylko Prometheus).
**Fix**: po zmianie configu serwisu albo `--force-recreate`, albo POST `/-/reload` dla
serwisów z lifecycle API (fleet-prometheus ma `--web.enable-lifecycle`). Rozważyć
wykrywanie zmiany plików config w deploy i wymuszanie recreate.
---
### Zbadać: chelsty + chelsty-infra `node_exporter` DOWN z VPS
**Data**: 2026-06-26
**Źródło**: sesja 2026-06-26 (`docs/sessions/2026-06-26.md`)
**Problem**: przy inwentaryzacji targetów floty do fleet-prometheusa, `node_exporter`
na chelsty i chelsty-infra był nieosiągalny z VPS — pominięte w scrape. To LTE edge
(intermittent uplink), więc DOWN może być normą, ale wymaga rozróżnienia: brak
node_exportera vs odcięty uplink vs zablokowany port.
**Fix**: ustalić, czy node_exporter w ogóle działa na obu chelsty (compose/proces),
czy jest osiągalny po Tailscale z VPS, i czy ma sens go scrape'ować mimo LTE
(prawdopodobnie tak — `up==0` na LTE = sygnał dla anomaly detection, nie fałszywy alarm).
---
### Supervisor nie enqueue'uje akcji remediacji przy `error`-state
**Data**: 2026-06-25 (powtórka sygnału z 2026-06-19)
**Źródło**: sesja 2026-06-25 (`docs/sessions/2026-06-25.md`)
**Problem**: Action Queue pusta mimo `System Status ERROR` widocznego w panelu.
Supervisor nie generuje `container_restart` / `redeploy` dla serwisów w stanie `error`.
Objaw zaobserwowany co najmniej dwukrotnie — wymaga izolowanego dochodzenia.
Podejrzane: supervisor może nie reagować na error-state jeśli źródłem są ghost kontenery
(błędne project-name), nie realne health-check failures.
**Fix**: zbadać osobno — sprawdzić, czy supervisor otrzymuje właściwe eventy od observera,
czy ma własną logikę de-duplifikacji blokującą enqueue.
**Update 2026-07-02**: ghost kontenery (bug B) zniknęły z VPS — jeśli objaw wróci,
hipoteza "źródłem są ghosty" jest już nieaktualna. UWAGA: ślepy supervisor na SATURN
(brak mountu repo, patrz sesja 2026-07-02) to INNY przypadek — nie mylić z tym bugiem.
**Update 2026-07-16** (sesja `docs/sessions/2026-07-16.md`, druga połowa dnia): dwie
głębsze przyczyny pustej kolejki znalezione i naprawione (petla supervisora zamrożona
~24h — patrz „Supervisor: pętla zamrożona…" w Zamkniętych; event flood 358k plików
paraliżujący reconcile — patrz „Event flood…" w Zamkniętych). Po obu fixach supervisor
tika i reconcile się kończy, ALE objaw z tego wpisu (brak `redeploy` mimo widocznego
`error` — elasticsearch/diskover na piha, ollama solaria) **nadal aktualny** — drift→action
nie domyka się mimo odetkanego mózgu. Zostaje otwarte jako osobne dochodzenie.
---
### Rozjazd world-state observera: panel pokazuje serwis NOMINAL przed jego istnieniem
**Data**: 2026-06-25
**Źródło**: sesja 2026-06-25 (`docs/sessions/2026-06-25.md`)
**Problem**: panel wykazał `fleet-prometheus` jako nominal na SOLARII zanim kontener
w ogóle istniał — observer `world_state` rozjechany z dockerem. Artefakt rejestracji
w manifeście bez realnego kontenera. Podobna klasa błędu jak ghost kontenery.
**Fix**: observer powinien weryfikować faktyczny stan kontenera przy budowaniu world_state
zamiast opierać się wyłącznie na zarejestrowanych serwisach.
---
### 🔴 BLOKUJĄCE — FLOTA-BOMBA: node-agent SSH mount ślepy po recreate
**Data**: 2026-06-11
**Źródło**: sesja lustro ssh shipping fix
**Problem**: solaria/piha/chelsty to stare **root** kontenery node-agenta (piha Created
2026-05-27, uid 0) — sprzed dodania `user: "1000:1000"` do bazowego compose. Ich override
montuje klucz SSH w `/root/.ssh`, co działa tylko dla uid 0. Pierwszy `--force-recreate` /
reboot hosta / update obrazu przełączy kontener na uid 1000 (`homelab`, HOME=/home/homelab)
i shipping eventów na VPS padnie z "Permission denied" — dokładnie jak na lustrze
(naprawione `a5a1352`). `ssh` w `_ship_events_to_vps()` nie ma `-i` i szuka klucza
w `$HOME/.ssh`.
**⚠️ NIE RECREATE node-agenta na solaria/piha/chelsty przed fixem.**
**Fix**: ujednolicić mount → `/home/homelab/.ssh` we wszystkich
`hosts/*/runtime/node-agent/docker-compose.override.yml` (wzór: `hosts/lustro/`)
ALBO dodać `-i $HOME/.ssh/id_rsa` w `_ship_events_to_vps()`.
---
### ha-diag-agent deploy ZABLOKOWANY (placeholder token)
**Data**: 2026-06-11
**Źródło**: sesja — deploy config merged (`5e9db5c`), `.env` na piha utworzony
(`/opt/homelab/config/ha-diag-agent/.env`, chmod 600) ale token = PLACEHOLDER.
**Blokada**: chelsty-ha offline → brak tokenu i połączenia.
**Do decyzji**: cel HA — chelsty-ha vs HA Ken (`homeassistant5` na piha; z kontenera
NIE `localhost`).
**Przed `shadow_mode=false`**: target restartu w supervisorze = nazwa kontenera
`homeassistant5`; curl endpointu HA z tokenem = HTTP 200.
---
### observer-poison-quarantine — review brancha (`78c9e4a`)
**Data**: 2026-06-11
**Źródło**: sesja — patch Codexa zachowany na `task/observer-poison-quarantine`, NIE w master.
**Do zrobienia**: zweryfikować, czy observer realnie wiesza się na malformed evencie
(poison NIE był przyczyną awarii lustra — hipoteza niezweryfikowana, obalona przez
verify-before-fix). Realny bug → merge; inaczej → drop brancha i worktree.
---
### node_agent.py — drobne sprzątanie shippingu
**Data**: 2026-06-11
**Źródło**: sesja lustro ssh shipping fix
1. **Stale komentarz** `node_agent.py:546-548` — twierdzi, że kontener "runs as root";
nieaktualne od `user: "1000:1000"`.
2. **Sukces shippingu na `logger.debug`** → podnieść do `info` lub dodać licznik —
działający shipping jest niewidoczny w logach przy INFO, co utrudniało diagnozę
(cicha awaria wyglądała identycznie jak ciche działanie).
---
### event-bloat: wyczyścić spłynięty backlog lustro na VPS
**Data**: 2026-06-11
**Źródło**: sesja — po fixie shippingu 7600+ plików backlogu spłynęło do
`/opt/homelab/events/lustro/` na VPS.
**Fix**: wyczyścić stare pliki (observer już je przetworzył); docelowo polityka retencji
w event-store.
---
### rsync `--omit-dir-times` (node-agent)
**Data**: 2026-06-09
**Źródło**: flota recovery session
**Objaw**: rsync exit code 23 po każdym push — `set-times` na katalogu `/opt/homelab/events/`
zwraca EPERM (oskar nie jest właścicielem katalogu; aerbot jest). Pliki są kopiowane poprawnie,
ale exit 23 zaśmieca logi i może maskować prawdziwe błędy.
**Fix**: dodać `--omit-dir-times` do wywołania `rsync` w `node-agent.py`.
**Lokalizacja**: `services/node-agent/src/node_agent.py` — wywołanie rsync w pętli push.
**Update 2026-06-11**: potwierdzone flotowo — każdy node loguje fałszywe
"Event shipping failed" (rsync code 23) co cykl, mimo że pliki przechodzą; katalogi
`/opt/homelab/events/*` na VPS należą do `aerbot`, klient nie ustawi na nich czasów.
---
### Deklaratywny zapis `oskar ∈ aerbot` w manifeście VPS
**Data**: 2026-06-09
**Źródło**: flota recovery — root cause: oskar spoza grupy aerbot(1000) → rsync Permission denied
**Problem**: przynależność do grupy jest zarządzana ręcznie (`usermod -aG 1000 oskar` ad-hoc).
Brak gwarancji po przeinstalowaniu VPS lub zmianie usera.
**Fix**: dodać do `hosts/vps/host.yaml` lub `hosts/vps/capabilities.yaml` sekcję
`users: oskar: groups: [aerbot]` — i wyegzekwować w deploy/bootstrap skrypcie VPS.
Alternatywa: zmienić właściciela `/opt/homelab/events/` na `oskar:oskar` i zaktualizować
node-agent deploy skrypty.
---
### Rozdzielenie worktree per task (agent.sh)
**Data**: 2026-06-09
**Źródło**: sesja — `homelab-codex-ws-node-onboarding` używany raz dla `task/node-onboarding`,
raz dla `task/fix-event-bloat` przez ręczne `git checkout`.
**Problem**: jeden worktree współdzielony przez dwa branche = anty-wzorzec. `git branch`
mogło wskazywać zły branch; `+` w listingu = pozornie "w innym worktree" ale nieprawda.
Prowadzi do commitowania na złej gałęzi.
**Fix**: egzekwować — jeden task = jeden worktree (`agent.sh new <task-name>`). Przy wejściu
do worktree zawsze `git branch --show-current` i weryfikacja `.agent-task`.
Długoterminowo: `agent.sh new` powinien odmawiać jeśli żądana gałąź jest już sprawdzona.
---

View file

@ -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).
---

View file

@ -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).
---

View file

@ -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 <node> <service>` **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/<node>/` 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).
---

View file

@ -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@<node> …`, 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.
---

View file

@ -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.

View file

@ -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.

View file

@ -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**: `kb/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`. R1R3 w toku po stronie subsystemu A.
---

View file

@ -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**: `kb/subsystems/fleet-inventory.md` (23 rozjazdy, pelna tabela tam).
**Weryfikacja 2026-07-02**: `kb/subsystems/fleet-inventory-verify.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**: `kb/subsystems/fleet-inventory-verify.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.

View file

@ -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 <user>` 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).

View file

@ -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/<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 `kb/subsystems/fleet-inventory-verify.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 `kb/subsystems/fleet-inventory-verify.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**: `kb/subsystems/fleet-inventory-verify.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:
<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 24 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)*

View file

@ -0,0 +1,24 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-08-03
links:
- ../services/job-deploy-runner.md
- ../runbooks/deploy-runner-install.md
---
# deploy-runner — co bylo zepsute (uzasadnienie zmiany toru redeployu)
## What was broken
The executor ran `scripts/deploy/deploy-node.sh <node> <service>` **inside its own
container**. That script ignores both arguments, and expects a repo at
`${HOME}/homelab-codex-ws` — inside the container `HOME=/home/homelab`, so it
exited at line 18 with `Error: Repository not found`. Behind that failure sat
three more: no `git`, no `docker` CLI in the image, and — had it ever got that
far — it would have deployed the **executor host's** entire service set, not the
action's target node/service. Every `healthcheck_failed → redeploy` dead-ended
(recon `kb/subsystems/recon-multiagent.md`, D14/D15).

View file

@ -0,0 +1,28 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-06-24
links:
- ../services/fleet-prometheus.md
- ../runbooks/fleet-prometheus-deploy.md
---
# fleet-prometheus — dlaczego osobny od domowego `prom`
## Why separate from the home `prom`
The home `prom` on **PIHA** is LAN-only and monitors the home network. This
instance is intentionally a **distinct** Prometheus dedicated to the distributed
fleet — hence the `fleet-` naming. The split is made explicit in every series:
- `external_labels: { fleet: "homelab-codex" }`
- job names prefixed/scoped for the fleet (`prometheus`, `fleet-node`)
It also avoids two anti-patterns the home `prom` has:
- **No `:latest`** — the image is pinned to `prom/prometheus:v3.5.0` (LTS).
- **No inline secrets**`prometheus.yml` is secret-free by contract (the home
prom embeds a plaintext HAOS token; we do not).

View file

@ -0,0 +1,54 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-07-09
links:
- ../services/gokapi.md
- ../runbooks/gokapi-cutover.md
---
# Gokapi — decyzje: storage, szyfrowanie E2E, siec
## Storage — lokalny dysk VPS (nie S3)
Decyzja Oskara: bez S3, dysk lokalny VPS. VPS ma tylko 80 GB SSD dzielone z
npm/outline/joplin/ai-cluster/fleet-prometheus — więc **ograniczenia
rozmiaru + brak globalnego domyślnego expiry to jedyna ochrona dysku**:
- `GOKAPI_MAX_FILESIZE=5120` (5 GB/plik; upstream default to 100 GB — za
dużo na ten dysk).
- `GOKAPI_MIN_FREE_SPACE=2048` (2 GB headroom zanim Gokapi odmówi uploadu;
upstream default 400 MB, za mało przy dzielonym dysku).
- **Gokapi NIE MA globalnego domyślnego expiry/limitu pobrań** — to wybór
per-upload w formularzu web. Nie da się tego wymusić przez env var ani
config. Praktyka: przy każdym uploadzie ustawiać rozsądne wartości (np.
**7 dni / 10 pobrań**), żeby wygasające linki faktycznie czyściły dysk.
## Szyfrowanie E2E — WŁĄCZONE (decyzja Oskara)
Gokapi ma 3 poziomy szyfrowania (żaden / lokalny / **end-to-end**). Wybór
robi się w kroku "Encryption" wizardu `/setup` przy pierwszym starcie — nie
ma env vara. Level 3 (E2E) = plik szyfrowany w przeglądarce przed uploadem,
serwer nigdy nie widzi treści w plaintext. Uwaga upstream: implementacja
szyfrowania nie była niezależnie audytowana; Firefox ma problemy z
pobieraniem zaszyfrowanych plików (znane ograniczenie, nie nasz bug).
Klucz szyfrowania trafia do `config.json` w `/app/config` — **to jest część,
którą trzeba backupować**, inaczej utrata configu = utrata dostępu do już
zaszyfrowanych plików.
## Sieć — bind tylko na Tailscale, nigdy 0.0.0.0
Port 53842 binduje się **wyłącznie** na Tailscale IP VPS-a
(`TAILSCALE_BIND_IP=100.95.58.48`), nigdy na `0.0.0.0`. Publiczny adres
Hetznera (`135.181.153.108`) w ogóle nie widzi tego portu — jedyna droga na
zewnątrz to `npm@VPS` (TLS na 443) → `share.okit.pl`. npm i gokapi żyją na
tym samym hoście jako osobne stacki compose; npm dociera do gokapi przez
Docker hairpin NAT po tym samym realnym interfejsie (ten sam trik co
`fleet-prometheus`/`nextcloud` — `127.0.0.1` by tu NIE zadziałało).
`GOKAPI_TRUSTED_PROXIES=172.16.0.0/12` mówi Gokapi, żeby ufał
`X-Forwarded-For` z tego zakresu (podsieć mostka Docker), bo źródłowy IP po
hairpinie to brama bridge'a, nie prawdziwy klient.

View file

@ -1,9 +1,19 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-07-30
links:
- ../incidents/2026-07-22-ha-dwie-instancje.md
---
# Home Assistant configs-as-code — design decisions
Status: **phase 1 (partial)**. `scripts/ha/deploy.sh` implements the write
path for the `api` adapter's automations/scripts/scenes scope (see "Deploy
path" and "Sync model" below); dashboards/helpers and the `docker-exec`
adapter have no write path yet. See `docs/backlog.md` for the tracking
adapter have no write path yet. See `kb/phases/backlog.md` for the tracking
entry.
## Phasing
@ -49,7 +59,7 @@ behind a common interface (`import.sh`/eventual `deploy.sh <instance>`):
|---|---|---|
| `ken` (RPi4, HAOS, LAN `192.168.31.7:8123`) | **api** | Canonical home instance since the 2026-07-22 cutover (see Incident log). HAOS has no SSH access, so there is no `docker exec`/filesystem path — only the HA REST/websocket API is reachable. Full `/config` import is deferred until an alternative access path exists; for now the api adapter's import scope is limited to what the API exposes: automations, scripts, scenes, dashboards. |
| `ken-legacy` (piha, container `homeassistant5`) | **docker-exec over SSH** (archive-only) | Pre-migration container instance, superseded by `ken` at 31.7 (see Incident log) — same container filesystem access as the old `ken` entry (`ssh oskar@piha "docker exec homeassistant5 ..."`). Import only, for historical reference; never a deploy target. |
| `chelsty-ha` | **api** | Reachable over Tailscale at `100.70.180.90:8123` (confirmed working path — `services/ha-diag-agent/DEPLOY.md` already curls this for health checks). Config-as-code deploy will reuse the same reachability, calling the HA REST/websocket API rather than shelling into the container. |
| `chelsty-ha` | **api** | Reachable over Tailscale at `100.70.180.90:8123` (confirmed working path — `kb/runbooks/ha-diag-agent-deploy.md` already curls this for health checks). Config-as-code deploy will reuse the same reachability, calling the HA REST/websocket API rather than shelling into the container. |
**Open**: a `file` adapter (direct bind-mount / SSH `rsync` to the config
directory, bypassing `docker exec`) is worth revisiting once SSH access to
@ -114,7 +124,7 @@ copied into the repo.
- A dedicated `deploy_agent` HA user account (admin rights, **local-only**
— never exposed through the public API/ingress) is created per instance,
mirroring the existing `diag_agent` account pattern documented in
`services/ha-diag-agent/DEPLOY.md`. Reusing `diag_agent` is explicitly
`kb/runbooks/ha-diag-agent-deploy.md`. Reusing `diag_agent` is explicitly
rejected — deploy tooling and the diagnostic agent must be revocable
independently.
- Long-lived access tokens for `deploy_agent` live at
@ -134,44 +144,13 @@ copied into the repo.
the existing Telegram bot / approval-queue pattern from
`services/control-plane/`.
## Incident log
### 2026-07-22 — two HA instances controlling the house in parallel
**Symptom**: automations firing twice from a single physical trigger — e.g.
`turn_on_led_nad_blatem_1` firing the same day from the same button press,
`mirror_on` at 04:30 and `gniazdka_w_lazience_on` at 05:00 all firing on
both instances.
**How detected**: comparing `last_triggered` from `restore_state` across the
two instances showed identical automation IDs firing at the same times on
both — the container on piha (`homeassistant5`, HA 2026.4.3, location_name
`KEN`, mounted at `/home/pi/homeassistant/config`) never actually stopped
running after the migration to the RPi4/HAOS instance at 192.168.31.7; it
stayed alive and MQTT-connected, so both were independently reacting to the
same physical events.
**Root cause**: `instances.yaml` had `ken` pointed at the piha container —
that was the pre-migration instance, not the real one. The actual home
instance had already moved to Home Assistant OS on a dedicated RPi4
(192.168.31.7:8123, ingress `ha.kapala.org` via NPM, confirmed HAOS via
observer :4357, HACS installed, 118 automations), but the repo never
followed the move.
**Decision**: 192.168.31.7 (HAOS/RPi4) is canonical `ken`. The piha
container is renamed `ken-legacy` in `instances.yaml`, `status: archived`.
Plan: archival import for historical reference → `docker stop` (not `rm`)
→ one week of observation → decide on `docker rm`. See `docs/backlog.md`
for the ha-diag-agent re-pointing and wind-down follow-ups this incident
generated.
## Decyzje operatora po audycie 2026-07-23
Zobacz `docs/audyt-automatyzacji-2026-07-23.md` (sekcja "Do decyzji operatora",
17 punktów) — poniżej wyłącznie decyzje, które doprowadziły do zmian w
fix-pack 1 (`task/ha-fix-pack-1`) albo świadomie do braku zmian. Reszta
checklisty (baterie/re-pairing czujników, kalibracje TRV, xiaomi_miot,
konsolidacja aliasów, higiena 4.x) zostaje otwarta w `docs/backlog.md`.
konsolidacja aliasów, higiena 4.x) zostaje otwarta w `kb/phases/backlog.md`.
- **Pkt 6 (klima salon: sunset ubija też ręczne chłodzenie?)** — decyzja:
NIE. „Klima salon: wyłącz…" (`1784804668795`) ma teraz respektować
@ -187,7 +166,7 @@ konsolidacja aliasów, higiena 4.x) zostaje otwarta w `docs/backlog.md`.
- **Pkt 7 (enforcer sleep mode gasi światła cyklicznie całą noc) i pkt 9
(konsolidacja czterech nocnych wyłączników)** — bez zmian w tym fix-packu.
Oba wchłania przyszły projekt „architektura night_mode" (patrz
`docs/backlog.md`) — punktowa łatka tu tylko dodałaby kolejny wariant do
`kb/phases/backlog.md`) — punktowa łatka tu tylko dodałaby kolejny wariant do
już przegęszczonego zestawu nakładających się automatyzacji (audyt 2.2).
- **Pkt 11 (OwnTracks: przywrócić czy skasować) i pkt 12 (Leave auto on:
batch 02 — włączyć z powrotem?)** — świadomie bez zmian; obie wymagają

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: decision
visibility: private
status: planned
updated: 2026-07-09
links: []
---
# Decyzje do podjęcia — filar dokumentów (moduły 2/3/4)
> Zbiorcza lista decyzji z przygotowania configów (2026-07-06, branch
@ -54,7 +63,7 @@ zdecydować, czy podbić concurrency, czy zostawić zapas na ollama/AI.
rsync/borg → SOLARIA (2 TB, ta sama LAN). Retencja: 7 dziennych +
4 tygodniowe + 6 miesięcznych. Offsite (np. restic → chmura) zostaje jako
future-note, poza zakresem tego etapu. Cron/skrypt deployowy powstaje przy
deployu modułu 2, nie teraz. Szczegóły: `services/paperless/README.md`.
deployu modułu 2, nie teraz. Szczegóły: `kb/services/paperless.md`.
- **4. Redis brokera: `requirepass`.** Broker (6380) dostaje hasło —
`PAPERLESS_REDIS_PASSWORD` w `.env` po obu stronach (paperless@PIHA,
@ -67,7 +76,7 @@ zdecydować, czy podbić concurrency, czy zostawić zapas na ollama/AI.
+ SOLARIA po NFS) świadomie zaakceptowane — indeks jest odtwarzalny
(`document_index reindex`), oryginałom nic nie grozi. Bez zmian w
configu; fallback-worker na PIHA zostaje. Szczegóły:
`services/paperless-worker/README.md`.
`kb/services/paperless-worker.md`.
- **6. Domeny: `kapala.org` (mesh, prywatne).** `paper.kapala.org`
(Paperless), `cloud.kapala.org` (Nextcloud) — potwierdzone, `*.okit.pl`
@ -90,7 +99,7 @@ zdecydować, czy podbić concurrency, czy zostawić zapas na ollama/AI.
maintainerów paperless-ngx (nieoficjalnie wspierany): ten sam obraz,
`command: celery --app paperless worker`, wspólny Redis+Postgres+storage,
identyczne ścieżki kontenerowe i numeryczny UID po obu stronach. Pełny
wynik badania + ryzyka: `services/paperless-worker/README.md`.
wynik badania + ryzyka: `kb/services/paperless-worker.md`.
- Storage dokumentów na PIHA; NFS export → SOLARIA po LAN
(192.168.31.5 → 192.168.31.70), nie Tailscale.
- AOF w Redis brokera (kolejka przeżywa restart — zero utraty zadań).

View file

@ -0,0 +1,27 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-06-26
links:
- ../subsystems/kb-overview.md
---
# KB — log decyzji (zamkniete vs otwarte)
## Decyzje — zamknięte vs otwarte
**Zamknięte:**
- Spine: Postgres + pgvector (nie Qdrant).
- Embed: **bge-m3** (multilingual, długi kontekst — pod polski lepszy niż multilingual-e5).
- Załączniki: indeksowane w **II turze** (MVP najpierw czysty tekst).
- Warstwa 3 startuje jako **cienki graf encji**; federacja przy zapytaniu dochodzi później (docelowo hybryda).
- Dokumenty: Nextcloud + Paperless-ngx.
**Otwarte:**
- **Transakcje:** agregator vs CSV, pokrycie mBanku, Revolut, koszt (filar #4).
- **Maile §design:** sizing archiwum / node (ile waży Gmail), unifikacja adaptera (jeden IMAP dla obu vs JMAP+IMAP osobno).
---

View file

@ -0,0 +1,36 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-07-09
links:
- ../services/nextcloud.md
- ../runbooks/nextcloud-cutover.md
---
# Nextcloud — decyzja: host = PIHA
## Decyzja: host = PIHA
Moduł 4 skłaniał się ku SOLARIA (mocniejszy host, bez wpływu na KB — patrz
niżej), ale Oskar zdecydował inaczej: **Nextcloud jest używany aktywnie**
(telefon, sync, rodzina) — sesyjna dostępność SOLARII (sync dogania się
dopiero po wybudzeniu hosta) nie jest akceptowalna dla tego workloadu.
Nextcloud musi być **always-on**, więc ląduje na PIHA mimo ciaśniejszego
RAM/CPU.
| | PIHA (wybrane) | SOLARIA |
|---|---|---|
| Dostępność | 24/7 (sync zawsze działa) | sesyjna — sync dogania się po wybudzeniu |
| RAM/CPU | ciasno nawet po module 0 (Nextcloud+PHP ≈ 0.51 Gi+) | 62 Gi RAM, 24 rdzenie — bez znaczenia |
| Storage | NVMe 477 G (dzielone z resztą) | NVMe 2 T |
| Ingress | npm lokalnie (ten sam host) | npm@PIHA proxuje po LAN do 192.168.31.70:8220 |
| Wpływ na KB | żaden — KB czyta własną kopię z archiwum na PIHA w obu wariantach | jw. |
`service.yaml` ma `owner_node: piha`; `env.example` ma `LAN_BIND_IP` PIHA
(192.168.31.5) i `TRUSTED_PROXIES` pod docker bridge (npm i nextcloud na
tym samym hoście — patrz komentarz w `env.example`). Compose zostaje
przenośne (ścieżki po konwencji `/opt/homelab/data`), gdyby host kiedyś
się zmienił.

View file

@ -0,0 +1,33 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-07-09
links:
- ../services/paperless.md
- ../runbooks/paperless-cutover.md
---
# Paperless — decyzja: split OCR serwis@PIHA + worker@SOLARIA
## Split OCR: serwis@PIHA + worker@SOLARIA (wynik badania)
Rozproszony worker **nie jest oficjalnie wspierany** przez Paperless-ngx, ale
jest wykonalny i potwierdzony przez maintainerów (GH discussion #3900): drugi
host odpala ten sam obraz z `command: celery --app paperless worker` i musi
widzieć **ten sam Redis, tego samego Postgresa i te same pliki**. Stąd:
- storage dokumentów leży na PIHA (bind mounty `/opt/homelab/data/paperless/*`)
i jest eksportowany przez **NFS po LAN** (nie Tailscale) do SOLARII;
- ścieżki w kontenerze (`/usr/src/paperless/{data,media,consume}`) muszą być
**identyczne** po obu stronach — payloady zadań i DB niosą ścieżki absolutne;
- pliki mają właściciela **numerycznego UID 1000** (`USERMAP_UID/GID=1000`
po obu stronach) — NFS przenosi numeryczne ID, nie nazwy;
- wbudowany worker na PIHA (nie da się go wyłączyć w stockowym obrazie) działa
jako wolny fallback, gdy SOLARIA śpi; zadania czekają w Redis (AOF włączone,
restart brokera nie gubi kolejki).
Szczegóły NFS (export na PIHA, mount na SOLARIA, ryzyko indeksu Whoosh) —
`kb/services/paperless-worker.md`.

View file

@ -1,3 +1,12 @@
---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-07-01
links: []
---
# Modul 1 — SSO Forgejo-OIDC (decyzja + wzorzec wpiecia)
> Fundament tozsamosci dla filaru dokumentow (i szerzej homelaba). Zapisuje decyzje

View file

@ -1,3 +1,13 @@
---
okf: "0.1"
type: decision
visibility: private
status: deprecated
updated: 2026-06-24
links: []
superseded_by: "kb/phases/backlog.md (kb/phases/backlog.md przejal ewidencje dlugu)"
---
# Tech Debt
## forgejo_runner (piha)

View file

@ -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ą `<hash>_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.

View file

@ -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-<ts>-...`, 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-<node>-<unixts>-…`
(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.

Some files were not shown because too many files have changed in this diff Show more