Compare commits

..

10 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
182 changed files with 2301 additions and 1730 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

@ -90,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

@ -130,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

@ -79,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).
@ -96,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

@ -120,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

@ -171,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

@ -108,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

@ -19,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

@ -10,7 +10,7 @@ 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

@ -21,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.
@ -100,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

@ -22,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

@ -46,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

@ -27,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

@ -31,7 +31,7 @@ links: []
### 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

@ -70,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
@ -111,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

@ -9,7 +9,7 @@ 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.
@ -70,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.
@ -122,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

@ -9,7 +9,7 @@ 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
@ -23,7 +23,7 @@ links: []
> 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

@ -19,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-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)

5
hosts/piha/README.md Normal file
View file

@ -0,0 +1,5 @@
# PIHA
Infrastructure + Automation Node.
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

5
hosts/saturn/README.md Normal file
View file

@ -0,0 +1,5 @@
# SATURN
Primary Development & Orchestration Node.
Dokumentacja: [kb/nodes/saturn.md](../../kb/nodes/saturn.md)

5
hosts/solaria/README.md Normal file
View file

@ -0,0 +1,5 @@
# SOLARIA
Compute / GPU / Inference Node.
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)

5
hosts/vps/README.md Normal file
View file

@ -0,0 +1,5 @@
# VPS
Public Edge + Ingress Node.
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,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-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

@ -131,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

@ -14,7 +14,7 @@ links: []
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

@ -10,8 +10,8 @@ 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)
@ -42,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

@ -20,5 +20,5 @@ 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).
(recon `kb/subsystems/recon-multiagent.md`, D14/D15).

View file

@ -13,7 +13,7 @@ links:
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
@ -59,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
@ -124,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
@ -150,7 +150,7 @@ 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ć
@ -166,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

@ -63,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,
@ -76,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`
@ -99,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

@ -29,5 +29,5 @@ widzieć **ten sam Redis, tego samego Postgresa i te same pliki**. Stąd:
restart brokera nie gubi kolejki).
Szczegóły NFS (export na PIHA, mount na SOLARIA, ryzyko indeksu Whoosh) —
`services/paperless-worker/README.md`.
`kb/services/paperless-worker.md`.

View file

@ -5,7 +5,7 @@ visibility: private
status: deprecated
updated: 2026-06-24
links: []
superseded_by: "kb/phases/backlog.md (docs/backlog.md przejal ewidencje dlugu)"
superseded_by: "kb/phases/backlog.md (kb/phases/backlog.md przejal ewidencje dlugu)"
---
# Tech Debt

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.

View file

@ -0,0 +1,51 @@
---
okf: "0.1"
type: incident
visibility: private
status: active
updated: 2026-08-03
links:
- ../phases/backlog.md
---
## Fix: paperless-worker@SOLARIA — dwa bugi configu, naprawione i zweryfikowane na żywo (2026-07-12)
**Kontekst.** Moduł 3 (`services/paperless-worker/`, split-host OCR worker) był
zdeployowany i brał zadania z kolejki, ale miał dwa bugi w compose:
1. **`command: celery ...` nie odpalał celery.** Obraz paperless-ngx
(`/sbin/docker-entrypoint.sh`) routuje każdy argument NIE zaczynający się
od `/` do `manage.py` — więc `celery` lądował jako nieznana subkomenda
Django, nie jako program. Fix: `command: /usr/sbin/gosu paperless
/usr/local/bin/celery ...` (ścieżka absolutna wchodzi w gałąź `exec "$@"`
entrypointu; `gosu paperless` z przodu bo ta gałąź nie dostaje automatycznego
gosu, inaczej proces poszedłby jako root i zepsuł właściciela plików na NFS).
2. **Brak współdzielonego `SCRATCH_DIR`.** Paperless@PIHA staguje wgrywany
plik w `/tmp/paperless` (domyślny `SCRATCH_DIR`) i niesie tę ścieżkę w
payloadzie zadania celery jako ścieżkę absolutną. Worker@SOLARIA miał
własny, lokalny `/tmp/paperless` — gdy odbierał zadanie zamiast workera
PIHA, padał `Cannot consume ...: File not found`. Fix: NFS volume
`paperless_scratch` (ten sam wzorzec co `data/media/consume`) na
`/opt/homelab/data/paperless/scratch` (już istniał na PIHA, `chown 1000:1000`),
mount na `/tmp/paperless` po obu stronach.
Oba fixy + uzasadnienie: `services/paperless/docker-compose.yml`,
`services/paperless-worker/docker-compose.yml`, `kb/services/paperless-worker.md`.
Zweryfikowane end-to-end na żywo (branch `task/paperless-worker-fix`, jeszcze
niezmergowany do master w momencie pisania tego wpisu): 3 dokumenty testowe
wrzucone do `consume/` na PIHA, jeden odebrany i dokończony przez worker@SOLARIA
(log: `ocrmypdf`/`tesseract` → `ConsumeTaskPlugin completed with: Success`),
zero `File not found`. Dokumenty testowe usunięte po teście (`document.delete()`
+ ręczny cleanup plików) — produkcyjne 6 dokumentów nietknięte.
**Otwarte (świadomie odłożone, nie blokuje działania):**
- `hosts/solaria/services.yaml` i `inventory/topology.yaml` nie mają wpisu
`paperless-worker` (SOLARIA ma tam tylko `node-agent`) — było zaplanowane w
cutover checkliście README jako krok "przy deployu", ale nigdy nie zrobione.
Bez tego wpisu supervisor/observer nie widzą tego serwisu w desired-state —
drift (np. worker padnie i nie wstanie) nie zostanie automatycznie wykryty
przez agent system, tylko przez brak przetwarzania kolejki.
- Test formalnego fallbacku (stop worker@SOLARIA → kolejka mieli na PIHA →
start → drenaż) nie był wykonany w tej sesji — mechanizm nie zmienił się
tym fixem (był już OK), ale warto zweryfikować przy okazji.

View file

@ -0,0 +1,48 @@
---
okf: "0.1"
type: incident
visibility: private
status: active
updated: 2026-08-03
links:
- ../phases/backlog.md
---
## Bug: ha-diag-agent emituje eventy z node="unknown" do katalogu innego węzła (2026-07-14) — ZROBIONE (2026-07-15, `f2ba81b`)
**Kontekst.** To był plik-truciciel z buga checkpointu wyżej:
`evt-unknown-1781254800-ha_update_available-homeassistant-951.json` w
`events/piha/`. Node w evencie = `unknown`, ale plik wylądował w katalogu `piha/`.
Sufiks `-951` to `_seq` emittera → agent nachodził długo, wyemitował 951 eventów,
wszystkie jako `node="unknown"`.
**Root cause (config-wiring).** Tożsamość agenta (`node_name`) i KATALOG eventów
pochodzą z DWÓCH niezależnych źródeł:
- `services/ha-diag-agent/src/ha_diag/config.py:20``node_name: str = "unknown"`
(domyślne, gdy env `NODE_NAME` nie dotrze do procesu w kontenerze).
- `services/ha-diag-agent/docker-compose.yml:12` → wolumen
`/opt/homelab/events/${NODE_NAME:-ha-diag}:/events``${NODE_NAME}` jest
interpolowane po stronie HOSTA (compose), a katalog jest dodatkowo twardo
przypięty do `piha` w `hosts/piha/runtime/ha-diag-agent/docker-compose.override.yml`.
Jeśli `NODE_NAME` trafi do interpolacji wolumenu/override (→ `piha`), ale NIE do
`environment:` procesu (albo `Settings.load()` przez `os.environ.setdefault` go nie
nadpisze), aplikacja czyta `node_name="unknown"` i pisze eventy `node="unknown"`
do katalogu `events/piha/`. Rozjazd między nazwą w evencie a katalogiem docelowym.
**Skutek.** Poza zatruciem checkpointu (już naprawione osobno): eventy `node="unknown"`
są bezużyteczne dla world_state (observer tworzy węzeł-widmo `unknown`, potem prune go
kasuje bo nie ma go w topologii) — realny sygnał z ha-diag na piha przepada.
**Fix — ZROBIONE (2026-07-15, `f2ba81b`, `docs/sessions/2026-07-15.md`).** Wariant (a):
`node_name` NIGDY nie może być `"unknown"` w produkcji. `config.py`
`Field(default="unknown", validate_default=True)` + validator odrzuca `""`/`"unknown"`;
`main.py``SystemExit(1)` FATAL przy braku `NODE_NAME`; `EventEmitter.__init__` jako
ostatnia bramka przed nazwą pliku eventu. +18 testów, 0 regresji. Zmergowany i
zdeployowany na PIHA (rebuild, `NODE_NAME=piha` dochodzi do procesu). Pliki
`evt-unknown-*` na VPS/PIHA: 0 (potwierdzone).
**Pozostaje osobno (druga warstwa obrony, nadal TODO):** observer/emitter powinien
docelowo odrzucać/kwarantannować event, którego `node` w treści != katalog docelowy —
dzisiejszy fix zamyka źródło (`unknown` nie powstaje), ale nie waliduje spójności
node↔katalog dla innych, przyszłych źródeł eventów.

View file

@ -0,0 +1,35 @@
---
okf: "0.1"
type: incident
visibility: private
status: active
updated: 2026-08-03
links:
- ../phases/backlog.md
---
## Bug: deploy-node.sh nie przebudowuje obrazu — deploy "OK" ale nowy kod nie wchodzi (2026-07-15) — ✅ ZROBIONE (2026-07-16, commit `77defff`)
**Objaw.** `deploy-node.sh <serwis>` robi `docker compose up -d` BEZ `--build`. Dla
serwisów z Dockerfile (ha-diag-agent, node-agent, llm-gateway, brain-watchdog, itd.),
gdy zmienia się TYLKO kod (nie compose/env), compose widzi "kontener działa, obraz ten
sam" → status `Running` (0.0s), NIE przebudowuje i NIE recreatuje. Nowy kod z repo NIE
wchodzi w życie mimo `git pull` i "Deployment Complete".
**Skutek — cicha rozbieżność repo↔runtime.** Deploy raportuje sukces, a kontener biega
na starym obrazie. Ugryzło DWA razy: fleet-prometheus (config nie wchodził bez
force-recreate) i ha-diag-agent 2026-07-15 (fix node_name był w repo `f2ba81b`, ale
`Running` zamiast rebuild — trzeba było ręcznego `docker compose up -d --build
--force-recreate`).
**Root cause.** deploy-node.sh (~linia 110) w pętli deployu: brak `--build` w wywołaniu
compose. Docker cache'uje obraz po tagu, nie po zawartości src/.
compose. Docker cache'uje obraz po tagu, nie po zawartości src/.
**Fix — ZROBIONE (2026-07-16, `77defff`).** deploy-node.sh wywołuje teraz `--build`
warunkowo, gdy serwis ma top-level `Dockerfile` (`test -f services/<svc>/Dockerfile`);
prebuilt serwisy bez `--build` (no-op). Zweryfikowane w boju na PIHA: 6 serwisów
(node-agent/ha-diag/brain-watchdog/llm-gateway → Building; vikunja/kb-postgres → prebuilt).
**Follow-up pozostawiony**: `agent-system` ma build w podkatalogach bez top-level
Dockerfile — niezarejestrowany przez tę detekcję, osobny task.

View file

@ -0,0 +1,40 @@
---
okf: "0.1"
type: incident
visibility: private
status: active
updated: 2026-08-03
links:
- ../phases/backlog.md
---
## Ollama SOLARIA: brak sterownika NVIDII — ZAMKNIĘTE (2026-07-16)
**Kontekst.** Cutover 2026-07-15 (`kb/runbooks/ollama-solaria-cutover.md`)
odkrył, że SOLARIA nie miała zainstalowanego żadnego sterownika NVIDII —
`nvidia-smi` nie istniał na hoście. `hosts/solaria/services.yaml` opisywał
ollama jako "GPU-backed" od dawna, ale to było aspiracyjne — Ollama zawsze
szła CPU-only. GPU reservation zakomentowana w
`services/ollama/docker-compose.yml` (`f57a01a`); item trafił do backlogu
jako blokujący fazę mailową embeddingów (moduł 5).
**Fix (2026-07-16).** Zainstalowany `nvidia-driver-595-open` z repo dystrybucji
(nie stary PPA `graphics-drivers` dla jammy — zdezaktywowany przez rename na
`.disabled`). RTX 4070 Ti SUPER 16GB, CUDA 13.2, `nvidia-smi` działa na
hoście. `nvidia-container-toolkit` był już obecny (doinstalowany jako
prerequisite przy cutoverze 07-15). GPU reservation przywrócona w compose.
Pomiar throughput GPU vs CPU baseline (0.79s/chunk) — patrz
`kb/phases/kb-m5-documents-ingest-fazy.md`, sekcja timing.
**Status:** ZAMKNIĘTE.
**Follow-upy pozostawione (osobne taski):**
- **Batching wywołań Ollamy** — przed fazą mailową (225k kopert). Sekwencyjne
wywołania `/api/embeddings` (nawet na GPU) będą wąskim gardłem przy takiej
skali; ocenić równoległość/batch API Ollamy.
- **`UNIQUE(envelope_id, chunk_index)` bez `model`** w `document_chunk`
(`services/kb-postgres/init/002_chunks.sql`) — re-embedding innym modelem
cicho no-opuje się przez istniejący constraint. Schema change do zrobienia
przy fazie 3 (patrz `kb/phases/kb-m5-documents-ingest-fazy.md`, sekcja "Idempotency"
kroku 6 embed).

View file

@ -37,7 +37,7 @@ 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`
→ one week of observation → decide on `docker rm`. See `kb/phases/backlog.md`
for the ha-diag-agent re-pointing and wind-down follow-ups this incident
generated.

View file

@ -0,0 +1,50 @@
---
okf: "0.1"
type: incident
visibility: private
status: active
updated: 2026-08-03
links:
- ../phases/backlog.md
---
## Cutover HA "ken": kontener piha to legacy, prawdziwy dom to RPi4/HAOS (2026-07-22)
**Data**: 2026-07-22
**Źródło**: recon — dwie instancje HA równolegle sterowały domem (kontener
`homeassistant5` na piha + RPi4 HAOS 192.168.31.7), patrz
`kb/decisions/ha-configs-as-code.md` sekcja "Incident log". `instances.yaml`
naprawiony w tej samej sesji: `ken` = 192.168.31.7 (api), `ken-legacy` =
dawny kontener piha (docker-exec, archived).
**Do zrobienia**:
1. **ha-diag-agent na piha**: przepiąć z `http://localhost:8123` (celuje w
legacy!) na `http://192.168.31.7:8123` — wymaga nowego tokenu
`diag_agent` wystawionego na instancji 31.7 (obecny token jest dla
kontenera piha i nie zadziała na nowym targecie).
2. **Wygaszenie `homeassistant5`**: import archiwalny do
`services/home-assistant/config/ken-legacy/``docker stop` (BEZ `rm`)
→ 7 dni obserwacji (upewnić się, że nic w domu nie polega na tym
kontenerze) → decyzja o `docker rm`.
3. ✅ ZROBIONE (2026-07-22) — **Adapter `api` w `import.sh` dla `ken`**:
automatyzacje/skrypty/sceny przez `/api/config/<domain>/config/<id>`
(REST), dashboardy/area+entity registry/`input_*` helpery przez websocket
API (`scripts/ha/lib/ha_api.py`, `ha_ws.py`, `import_api.py`). Pierwszy
realny import `ken` zaimportował 118 automatyzacji, 5 skryptów, 3 sceny,
7 dashboardów (default + 6 named; jeden zarejestrowany dashboard nigdy
nie skonfigurowany — `config_not_found`, odnotowany w raporcie, nie
twardy błąd). Pełny import `/config` pozostaje poza zasięgiem (HAOS bez
SSH) — patrz DESIGN.md.
4. ✅ ZROBIONE (2026-07-22) — **`scripts/ha/deploy.sh`, adapter `api`, zakres
automations/scripts/scenes**: drift-check (świeży re-import vs. `HEAD`,
dowolna różnica poza plikami z tego deployu = abort z diffem) →
walidacja (lokalny sanity check + `check_config` na instancji) → zapis
per obiekt (`POST /api/config/<domain>/config/<id>`) → verify (GET +
porównanie, bez auto-rollbacku). `--dry-run` zweryfikowany na żywym
`ken` (read-only, bez różnic). Testy offline:
`scripts/ha/tests/test_deploy_api_offline.sh`. Poza zakresem: dashboardy/
helpery (WS, brak mutującej komendy), adapter `docker-exec`, DELETE
obiektów usuniętych z repo (tylko ostrzeżenie).
---

View file

@ -0,0 +1,27 @@
---
okf: "0.1"
type: incident
visibility: private
status: active
updated: 2026-06-25
links:
- ../subsystems/deployment.md
---
# ZNANY BUG — `deploy.sh vps` niszczy control-plane (2026-06-25)
## ⚠️ ZNANY BUG — `deploy.sh vps` niszczy control-plane (2026-06-25)
`deploy.sh vps` uruchamia `deploy-node.sh` w pętli po wszystkich serwisach VPS, w tym
`control-plane`. Pętla używa innego `COMPOSE_PROJECT_NAME` niż `deploy-local.sh`
(który uruchamiany jest z `cwd=services/control-plane`). Niezgodność project-name powoduje
`Recreate``No such container``set -e` przerywa pętlę → **observer, supervisor,
executor i operator-ui znikają z VPS.**
**Dopóki bug nie zostanie naprawiony (backlog — Krytyczny):**
- Do deployu control-plane używać: `ssh -t vps 'cd ~/homelab-codex-ws && cd services/control-plane && bash deploy-local.sh'`
- Inne serwisy VPS deployować punktowo: `deploy-node.sh` z `--service <name>` lub przez SSH + `docker compose up -d`
- **NIE uruchamiać `deploy.sh vps` bez pełnej świadomości ryzyka.**
---

71
kb/phases/backlog.md Normal file
View file

@ -0,0 +1,71 @@
---
okf: "0.1"
type: phase
visibility: private
status: active
updated: 2026-08-03
links:
- ../decisions/backlog-m1-solaria-prune-mitigation.md
- ../decisions/backlog-deploy-runner-instalacja.md
- ../decisions/backlog-disk-cleanup-zepsuty.md
- ../decisions/backlog-deploy-host-pusty.md
- ../incidents/2026-07-22-ha-ken-cutover-legacy.md
- ha-configs-as-code.md
- monitoring-floty-prometheus.md
- ../decisions/backlog-aktywne.md
- ../decisions/backlog-zamkniete.md
- ../decisions/backlog-anomaly-detection-liveness.md
- ../decisions/backlog-rozjazdy-repo-rzeczywistosc.md
- ../decisions/backlog-uid-gid-flota.md
- ../incidents/2026-07-12-observer-checkpoint-leksykalny.md
- ../incidents/2026-07-14-ha-diag-agent-node-unknown.md
- ../incidents/2026-07-12-deploy-local-ghost-kontenery.md
- ../incidents/2026-07-12-paperless-worker-config.md
- ../decisions/backlog-kb-serwisy-poza-monitoringiem.md
- ../incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md
- ../incidents/2026-07-16-ollama-solaria-brak-sterownika.md
- ../decisions/backlog-followupy-etap0.md
---
# Tech-debt backlog
Centralny tracker tech-długu i znanych usterek. Wpisy ze sesji — dodawaj z datą i kontekstem.
---
## Spis rozbitych elementow
> Ten plik byl monolitem 72 KB mieszajacym cztery typy OKF. W etapie 2
> zostal rozbity per typ; ponizej spis powstalych dokumentow. Tresc
> pozycji nie byla redagowana — kazda sekcja `##` trafila do wlasnego
> pliku w calosci.
### Decyzje / pozycje backlogu
- [M1 aktywna na SOLARII — cleanup node-agenta wyłączony do czasu R1 (2026-08-04)](../decisions/backlog-m1-solaria-prune-mitigation.md)
- [deploy-runner: instalacja na węzłach + E2E redeployu (2026-08-03)](../decisions/backlog-deploy-runner-instalacja.md)
- [disk_cleanup w executorze jest zepsuty tak samo jak był redeploy (2026-08-03)](../decisions/backlog-disk-cleanup-zepsuty.md)
- [`scripts/deploy/deploy-host.sh` to pusty plik (2026-08-03)](../decisions/backlog-deploy-host-pusty.md)
- [Aktywne](../decisions/backlog-aktywne.md)
- [Zamknięte](../decisions/backlog-zamkniete.md)
- [Anomaly detection liveness — mózg uczy się wzorca dobowego per node (pomysł 2026-06-26)](../decisions/backlog-anomaly-detection-liveness.md)
- [Rozjazdy repo<->rzeczywistosc (z inwentaryzacji 2026-06-30)](../decisions/backlog-rozjazdy-repo-rzeczywistosc.md)
- [Tech-debt: globalny porządek uid/gid/uprawnień we flocie (2026-07-10)](../decisions/backlog-uid-gid-flota.md)
- [Nowe serwisy KB nie sa w monitoringu (desired-state)](../decisions/backlog-kb-serwisy-poza-monitoringiem.md)
- [Follow-upy z etapu 0 (truth cleanup, 2026-07-29)](../decisions/backlog-followupy-etap0.md)
### Incydenty
- [Cutover HA "ken": kontener piha to legacy, prawdziwy dom to RPi4/HAOS (2026-07-22)](../incidents/2026-07-22-ha-ken-cutover-legacy.md)
- [Bug: checkpoint observera po ścieżce leksykalnej — kruchy, zatruwa węzeł na zawsze (2026-07-12)](../incidents/2026-07-12-observer-checkpoint-leksykalny.md)
- [Bug: ha-diag-agent emituje eventy z node="unknown" do katalogu innego węzła (2026-07-14) — ZROBIONE (2026-07-15, `f2ba81b`)](../incidents/2026-07-14-ha-diag-agent-node-unknown.md)
- [Bug: deploy-local.sh control-plane pada na ghost-kontenerach i zostawia mózg rozłożony (2026-07-12)](../incidents/2026-07-12-deploy-local-ghost-kontenery.md)
- [Fix: paperless-worker@SOLARIA — dwa bugi configu, naprawione i zweryfikowane na żywo (2026-07-12)](../incidents/2026-07-12-paperless-worker-config.md)
- [Bug: deploy-node.sh nie przebudowuje obrazu — deploy "OK" ale nowy kod nie wchodzi (2026-07-15) — ✅ ZROBIONE (2026-07-16, commit `77defff`)](../incidents/2026-07-15-deploy-node-nie-przebudowuje-obrazu.md)
- [Ollama SOLARIA: brak sterownika NVIDII — ZAMKNIĘTE (2026-07-16)](../incidents/2026-07-16-ollama-solaria-brak-sterownika.md)
### Fazy
- [Nowy podprojekt: Home Assistant configs-as-code (szkielet)](ha-configs-as-code.md)
- [Plan: Monitoring floty — Prometheus jako źródło prawdy](monitoring-floty-prometheus.md)

View file

@ -0,0 +1,23 @@
---
okf: "0.1"
type: phase
visibility: private
status: active
updated: 2026-08-03
links:
- backlog.md
---
## Nowy podprojekt: Home Assistant configs-as-code (szkielet)
**Data**: 2026-07-21
**Branch**: `task/ha-skeleton`
Szkielet struktury dla `services/home-assistant/` — configs-as-code dla
instancji HA (`ken` na PIHA, `chelsty-ha`). Na razie tylko struktura +
read-only import (`scripts/ha/import.sh`), bez deployu. Fazowanie, wybór
adaptera per instancja, model sync i otwarte pytania —
`kb/decisions/ha-configs-as-code.md`.
---

View file

@ -15,7 +15,7 @@ links: []
## STATUS: prerekwizyt RAM SPELNIONY (2026-07-02)
Faza 1 (audyt read-only) + faza 2 (egzekucja po review Oskara) wykonane —
szczegoly: `docs/infra/piha-slim-audit-2026-07-02.md` (sekcja "Korekta po review
szczegoly: `kb/audits/piha-slim-2026-07-02.md` (sekcja "Korekta po review
+ egzekucja").
- **Kryterium >= 1.5Gi available: SPELNIONE.** Przed egzekucja: 2.8Gi available
@ -37,7 +37,7 @@ Zwolnic RAM na PIHA (dzis: 3.1Gi available, swap 2G uzyty) tak, by lekki Paperle
serwis wszedl z zapasem, nie na styku swap.
## Wymogi
- Audyt 33 shadow-kontenerow (lista w `docs/infra/inventory-2026-06-30.md`).
- Audyt 33 shadow-kontenerow (lista w `kb/subsystems/fleet-inventory.md`).
- Zidentyfikowac kandydatow do usuniecia/przeniesienia/wylaczenia:
- **elasticsearch 1Gi** — kto tego uzywa? (wikijs? diskover?) — jesli martwy, ubic
- **diskover** — jednorazowy indekser? czy chodzi ciagle bez potrzeby?

View file

@ -13,7 +13,7 @@ links:
## Phase 2 — `documents-ingest-paperless` (Paperless -> envelope adapter)
Module 5, phase 2 (`docs/kb/modules/05-faza2-plan.md`, §4.2-4.3, §6 step 5).
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, never
writes to Paperless) and inserts them as `source='paperless'` rows into the
`envelope` table on kb-postgres, reusing `kb_mail.envelope.Envelope` /
@ -133,7 +133,7 @@ this commit.
## Phase 2 step 6 — `documents-ingest-embed` (chunk + embed)
Module 5, phase 2, plan step 6 (`docs/kb/modules/05-faza2-plan.md`, §6 step 6,
Module 5, phase 2, plan step 6 (`kb/phases/kb-m5-faza2.md`, §6 step 6,
§2 decision 3). Reads `entities[type=content].text` off every `source='paperless'`
envelope, chunks it, calls Ollama (`POST /api/embeddings`, model `bge-m3`) for
each chunk, and inserts the result into `document_chunk`
@ -311,7 +311,7 @@ kb-postgres@PIHA:
## Phase 3 step 4 — retrieval cascade (`documents_ingest.retrieval`) + quality gate
Module 5, phase 3, plan step 4 (`docs/kb/modules/05-faza3-plan.md`, §6). Two retrieval
Module 5, phase 3, plan step 4 (`kb/phases/kb-m5-faza3.md`, §6). Two retrieval
paths, both `query_text -> chunk hits (dist, source)` — the intended clean API surface for
phase 4's kb-query, not just this eval:
@ -328,7 +328,7 @@ phase 4's kb-query, not just this eval:
### Quality gate
`eval/queries.yaml` — 7 queries transcribed 1:1 from the phase-2 pilot baseline
(`docs/kb/eval/retrieval-pilot-2026-07-16.md`, left untouched — this is its versioned working
(`kb/phases/kb-m5-eval-retrieval-pilot.md`, left untouched — this is its versioned working
copy) with expected envelope / kind (`hit`, `grey_zone`, `negative_control`,
`negative_control_borderline`) per query.
@ -373,7 +373,7 @@ short-circuit (stage 2 never queried), and both query entry points embedding exa
## Phase 3 step 5 — cyclic ingest (`documents-ingest-cyclic`) + systemd timer
Module 5, phase 3, plan step 5 (`docs/kb/modules/05-faza3-plan.md`, §7). Orchestrates one
Module 5, phase 3, plan step 5 (`kb/phases/kb-m5-faza3.md`, §7). Orchestrates one
run of the recurring ingest pipeline: `paperless_adapter.run()` (new `source='paperless'`
envelopes) → `chunk_embed.run()` (new `document_chunk` rows) →
`summarize.run_summarize(backend='anthropic')` (new `document_summary` rows,

View file

@ -545,7 +545,7 @@ zeby dalo sie uruchomic partiami i zweryfikowac progres bez czekania na cale 225
rzedu dziesiatek-set chunkow/s. Caly pilot (23k chunkow) → **rzedu minut**, nie wymaga
specjalnego batchowania/partii.
- **Skala docelowa (70k zalacznikow z maili)**: modul 5 faza-1 to swiadomie **probka, nie
bulk** (`jobs/documents-ingest/README.md` — decyzja architektoniczna). Realny wolumen
bulk** (`kb/phases/kb-m5-documents-ingest-fazy.md` — decyzja architektoniczna). Realny wolumen
ktory trafi do embeddingu zalezy od (a) throughput OCR-workera na SOLARII (modul 3) —
**to jest waskie gardlo skalowania, nie embedding** — oraz (b) filtra selektywnosci
(decyzja #6). Sam embedding bge-m3 nie bedzie bottleneckiem nawet przy tysiacach

View file

@ -505,7 +505,7 @@ aktywnych chunków, N większe niż liczba kopert, no-summaries short-circuit)
pakietu przechodzi.
**Eval-set utrwalony**: `jobs/documents-ingest/eval/queries.yaml` (7 zapytań z pilota
07-16, 1:1 z `docs/kb/eval/retrieval-pilot-2026-07-16.md`, ten plik pozostał nietknięty —
07-16, 1:1 z `kb/phases/kb-m5-eval-retrieval-pilot.md`, ten plik pozostał nietknięty —
`queries.yaml` to jego wersjonowana kopia robocza). Skrypt bramki (read-only, integracyjny,
**nie wchodzi do pytest**): `jobs/documents-ingest/eval/retrieval_eval.py`.
@ -700,7 +700,7 @@ streszczeń). Kandydaci na pierwsze strony (encje z pilota): PZU/WARTA (polisy),
## 9. Poza zakresem fazy 3
Granice planu — wszystko poniżej jest świadomie odłożone, z istniejącym miejscem w
roadmapie (`docs/kb/kb-00-overview.md` „Stan etapów/Backlog", sesja
roadmapie (`kb/subsystems/kb-overview.md` „Stan etapów/Backlog", sesja
`docs/sessions/2026-07-16.md`, backlog operatora):
| Temat | Gdzie zakotwiczone | Kiedy |

View file

@ -71,10 +71,10 @@ zegarem, master trzyma to samo wewnątrz `EmbedRouter` (też injektowalny zegar)
| `services/kb-query/{README,env.example,service.yaml,docker-compose.yml}` | ~±58 | duplikat | **porzuć** | inny (porzucony) schemat env `OLLAMA_PIHA_URL`; master bogatszy (testy A/B/C, rename `OLLAMA_URL`→`EMBED_PRIMARY_URL`) |
| `packages/kb-retrieval/src/kb_retrieval/embed.py` (`timeout_s` w `embed_chunk`) | +16 | duplikat funkcji | **porzuć** | master osiąga twardy timeout przez `asyncio.wait_for` bez zmiany współdzielonego pakietu — mniejsza powierzchnia zmian, ten sam efekt |
| `jobs/documents-ingest/eval/retrieval_eval.py` (`--transport {direct,http}` + `--base-url`) | +109 | **unikalna wartość** | **cherry-pick** | plan §2 decyzja 6 / §9 (bramka HTTP-equivalence) — **na masterze w ogóle nie istnieje**; e7625cd nie tknął tego pliku, patch aplikuje się czysto; kod woła tylko `GET /search` i czyta `envelope_id`/`dist`/`source` — w pełni zgodny z odpowiedzią mastera |
| `jobs/documents-ingest/README.md` | +6 | **unikalna wartość** | **cherry-pick** | dokumentacja powyższego, idzie w parze |
| `kb/phases/kb-m5-documents-ingest-fazy.md` | +6 | **unikalna wartość** | **cherry-pick** | dokumentacja powyższego, idzie w parze |
| `docs/sessions/2026-07-27-kb-f4-fallback.md` | +189 | **unikalna wartość** | **adaptuj** | jedyny zapis: (1) znalezisko osieroconego natywnego `ollama.service` na PIHA + jego wyłączenie 2026-07-27 i backlog odinstalowania, (2) kalibracja live ollama-piha (GO: peak ~983 MiB, ~4.25.3 s/embed), (3) metodologia i wyniki bramki §9 (HTTP-equivalence 0 rozbieżności; sol-down Δ~3e-4), (4) rsync-deploy → dirty working tree na PIHA. Wciągnąć z dopiskiem redakcyjnym, że zmergowana implementacja to **inny kod** (e7625cd) i wyniki bramki wymagają powtórki |
| `services/ollama-piha/*` (5 plików) | +155 | duplikat | **porzuć** | wersja mastera lepsza: named volume `ollama_piha_models` (uzasadnienie uid-pattern PIHA), healthcheck sprawdza obecność `bge-m3`, bind tylko 127.0.0.1+LAN |
| `hosts/piha/runtime/ollama-piha/docker-compose.override.yml` | +13 | duplikat + **1 unikalny fakt** | **adaptuj (mikro)** | ten sam `mem_limit: 2560m`; ale komentarz brancha zawiera potwierdzony pomiar (peak ~983 MiB), a master wciąż mówi „Confirm/trim after live calibration" — dopisać wynik kalibracji do komentarza override'u i/lub sekcji „Calibration" w `services/ollama-piha/README.md` |
| `hosts/piha/runtime/ollama-piha/docker-compose.override.yml` | +13 | duplikat + **1 unikalny fakt** | **adaptuj (mikro)** | ten sam `mem_limit: 2560m`; ale komentarz brancha zawiera potwierdzony pomiar (peak ~983 MiB), a master wciąż mówi „Confirm/trim after live calibration" — dopisać wynik kalibracji do komentarza override'u i/lub sekcji „Calibration" w `kb/services/ollama-piha.md` |
| `hosts/piha/services.yaml` | ±26 | duplikat | **porzuć** | master ma własny wpis `ollama-piha` + soft-dependency kb-query; drobna różnica (`offline_required: true` na branchu vs `false` na masterze) — master źródłem prawdy |
---
@ -107,7 +107,7 @@ skonfigurowanego → `EmbedBackendError`, `fallback_status` (up/unconfigured), s
## 4. Rekomendacja zbiorcza (lista do zatwierdzenia)
1. **S1 — cherry-pick**: `retrieval_eval.py --transport http --base-url` + akapit w
`jobs/documents-ingest/README.md` (plan §2 D6/§9; aplikuje się czysto, zero zależności
`kb/phases/kb-m5-documents-ingest-fazy.md` (plan §2 D6/§9; aplikuje się czysto, zero zależności
od porzuconego kodu brancha).
2. **S2 — adaptuj**: `docs/sessions/2026-07-27-kb-f4-fallback.md``docs/sessions/`
z dopiskiem redakcyjnym na górze (implementacja z tej sesji porzucona na rzecz
@ -116,7 +116,7 @@ skonfigurowanego → `EmbedBackendError`, `fallback_status` (up/unconfigured), s
3. **S3 — adaptuj**: luki testowe T1 + T2 (T3 opcjonalnie) do `test_embed_router.py`.
4. **S4 — adaptuj (mikro)**: wynik kalibracji 2026-07-27 (peak ~983 MiB, ~4.25.3 s,
werdykt GO) do komentarza `hosts/piha/runtime/ollama-piha/docker-compose.override.yml`
i sekcji Calibration w `services/ollama-piha/README.md` — pomiar dotyczył kontenera
i sekcji Calibration w `kb/services/ollama-piha.md` — pomiar dotyczył kontenera
ollama-piha (ta sama konfiguracja: obraz, `OLLAMA_KEEP_ALIVE=0`, `mem_limit 2560m`),
więc **przenosi się** na wersję mastera; różni się tylko storage (bind vs named
volume), co nie wpływa na RAM/latencję.
@ -130,7 +130,7 @@ skonfigurowanego → `EmbedBackendError`, `fallback_status` (up/unconfigured), s
(3d4ee38) i worktree `~/homelab-codex-ws-kb-f4-fallback` po zakończeniu salvage.
- **(b) Powtórka testu sol-down na żywym masterze**: kalibracja i bramka z 2026-07-27
dotyczyły **innego kodu** (`fallback.py`, env `OLLAMA_PIHA_URL`) — na wdrożonym
e7625cd trzeba przejść testy A/B/C z `services/kb-query/README.md` oraz bramkę
e7625cd trzeba przejść testy A/B/C z `kb/services/kb-query.md` oraz bramkę
`retrieval_eval.py --transport http` (po S1): HTTP-equivalence przy SOLARIA-up
(identyczne `dist`) i sol-down (Δ≤epsilon, kolejność top-k identyczna; baseline
z 27.07: Δ~3e-4). Symulacja wg README: `EMBED_PRIMARY_URL=http://192.0.2.1:11434`

View file

@ -68,7 +68,7 @@ links: []
### 1.3 PIHA — budżet RAM (istotne dla decyzji D2 / lokalnego fallbacku)
- Audyt `docs/infra/piha-slim-audit-2026-07-02.md`: po "bezpiecznym usuń"
- Audyt `kb/audits/piha-slim-2026-07-02.md`: po "bezpiecznym usuń"
(elasticsearch+diskover+stary llm-gateway) available rosło z **2.9Gi do ~3.8Gi**
(na 8 GB total, +4 GB swap). To jest **jedyna zweryfikowana liczba w repo i ma
3 tygodnie** — od tego dnia na PIHA doszły (GitOps, z `mem_limit`): paperless+db+broker
@ -97,7 +97,7 @@ domeny `*.kapala.org`:
(cert #51 *.okit.pl, cert osobny dla *.kapala.org — sesje ją tylko konfigurują
ręcznie/SQL-em, nigdy nie deployują z repo). kb-query dogania się do tego samego
wzorca: nowy vhost w `npm@PIHA`, TLS z **już istniejącego** wildcard `*.kapala.org`
(pokrywa `paper.`/`cloud.`/`vikunja.kapala.org` — `docs/kb/modules/DECYZJE-do-podjecia.md`
(pokrywa `paper.`/`cloud.`/`vikunja.kapala.org` — `kb/decisions/kb-dokumenty-otwarte.md`
#6) — **żaden nowy certyfikat nie jest potrzebny**.
- **DNS — dwie warstwy, obie trzeba dotknąć** (lekcja `okit-cloudflare-migracja.md`
§"WAZNE: split-horizon DNS"): (a) Cloudflare rekord A → Tailscale IP PIHA
@ -261,7 +261,7 @@ przed backfillem" z fazy 3 §3.1 (zmierz, obejrzyj, dopiero wtedy zaufaj progowi
### Decyzja 3 — Linki do źródeł: paperless vs gmail
**Paperless**: URL do dokumentu — **do zweryfikowania na żywym Paperless przed
implementacją** (repo nie ma zapisanego przykładu, `docs/kb/modules/02-paperless-service.md`
implementacją** (repo nie ma zapisanego przykładu, `kb/phases/kb-m2-paperless.md`
dokumentuje tylko subdomenę, nie ścieżkę). Kandydat wg konwencji paperless-ngx UI:
`https://paper.kapala.org/documents/<id>/details` (Angular routing) — `envelope_id`
`paperless:<id>` już niesie surowy `<id>` do wstawienia. Krok implementacji: jeden
@ -471,7 +471,7 @@ Jedna strona (Jinja2 template + vanilla JS + CSS, serwowane z tego samego FastAP
- Pole zapytania + submit (Enter albo przycisk).
- Wyniki grupowane po `envelope_id` (dokument), w obrębie dokumentu chunki
posortowane po `dist`.
- Kolorowanie progów (progi z fazy 3, `docs/kb/modules/05-faza3-plan.md` §1.2,
- Kolorowanie progów (progi z fazy 3, `kb/phases/kb-m5-faza3.md` §1.2,
zweryfikowane bramką): `dist < 0.45` zielony, `0.450.55` żółty, `> 0.55`
**nie renderować wyniku**, tylko komunikat "brak odpowiedzi w KB" (żółta/czerwona
strefa nadal renderuje wynik z ostrzeżeniem wizualnym; czerwona = brak sensownego

View file

@ -0,0 +1,53 @@
---
okf: "0.1"
type: phase
visibility: private
status: active
updated: 2026-08-03
links:
- backlog.md
---
## Plan: Monitoring floty — Prometheus jako źródło prawdy
**Data**: 2026-06-22
**Źródło**: sesja 2026-06-22 (`docs/sessions/2026-06-22.md`)
**Decyzja**: Prometheus (pull, `up{}`) zastępuje warstwę WYKRYWANIA liveness
(node-agent shipper + rsync/ssh + event-store + observer prune/checkpoint + ręczne TTL)
— przyczynę nawracających awarii (uid≠1000, ślepy ssh-mount, bloat ~242k eventów,
race prune↔checkpoint, NOMINAL-bez-TTL). Osobny fleet-Prometheus pod GitOps, **nie**
adopcja domowego instance PIHA. **BEZ** Alertmanagera — alert przez brain-watchdog.
Placement: VPS. Granica: zostają supervisor (remediacja), observer/panel, ha-diag-agent,
historia incydentów, out-of-band watchdog.
> Zastępuje wcześniejszy szkic (blackbox + Alertmanager) z sesji 2026-06-17.
**Kroki (priorytetowo)**:
1. Scaffold serwisu `fleet-prometheus` pod GitOps (worktree `task/fleet-prometheus`,
wzorzec `services/vikunja/`): compose + `env.example` + `service.yaml` + README +
`healthcheck.sh`; rejestracja w `hosts/vps/services.yaml` + `inventory/topology.yaml`;
exposure `tailscale-internal`; pusty scrape na start (self + lokalny `node_exporter` VPS).
2. ✅ ZROBIONE (2026-06-26, commit `7d4014e`) — Inwentaryzacja nodów floty `100.x` do
scrape. Dodane: piha/solaria/lustro (`node:` label), vps zachowany. Saturn pominięty
(workstation), chelsty/chelsty-infra pominięte (node_exporter down z VPS → osobny wpis).
3. Container-layer exporter (cAdvisor lub lekki docker-state) — `node_exporter` nie widzi
kontenerów.
4. ✅ ZROBIONE (2026-06-30, commit `d417000`) — Reguły liveness (`up==0 for: 5m`).
`rules/liveness.yml`: `NodeDown expr up{node=~"vps|piha"}==0 for 5m severity critical`.
Only always-on (vps, piha); solaria/lustro świadomie wykluczone (intermittent → anomaly
detection). Deploy: fleet-prometheus Recreated (zmiana compose), reguła inactive=poprawnie.
5. ✅ ZROBIONE (2026-06-30, commit `62d6fc0`) — brain-watchdog: drugie wejście — poll
Prometheus `/api/v1/alerts` (`firing`) → Telegram. Architektura A: dwa niezależne tory,
mózg NIETKNIĘTY, debounce per-alert (klucz alertname:node w state.json). 12 testów pass.
PENDING zamknięty 2026-07-02: poll POTWIERDZONY reconem (obraz zbudowany po `62d6fc0`,
`PROMETHEUS_URL` w `.env` i w env kontenera, zero poll failed) — patrz
`kb/subsystems/fleet-inventory-verify.md`.
✅ END-TO-END UDOWODNIONE 2026-07-06 (Etap 0 cutoveru): tor Prometheus →
watchdog → Telegram potwierdzony w produkcji testem `AlertTestEtap0`
(firing → log polla → Telegram → rollback) — patrz `docs/sessions/2026-07-06.md`.
6. Rotacja tokenu HAOS w domowym prom (plaintext).
7. Przepięcie observer / panel `agents.okit.pl` na Prometheus jako źródło — największy
znak zapytania przy cutoverze.
8. Parallel-run obok rury eventowej; cutover dopiero gdy Prometheus-truth się udowodni.
---

View file

@ -164,7 +164,7 @@ Skutek uboczny do zaakceptowania świadomie: syntetyczne `node_stale`/
wyłączeniu **wcześniej, ale nie liczniej** — te eventy już dziś powstają co noc
(21:32/21:39 dla lustro, każdorazowo dla solaria). Cutover nie zwiększa wolumenu
alertów. Docelowe wyciszenie planowych okien off to wątek anomaly-detection
z backlogu (`docs/backlog.md:390-406`) — **niezależny od cutoveru i nieblokujący**;
z backlogu (`kb/phases/backlog.md:390-406`) — **niezależny od cutoveru i nieblokujący**;
`NodeDown` dla solaria/lustro słusznie pozostaje wyłączony.
### Rekomendowany mapping (potwierdzenie rekomendacji z recon F/Etap 2)

View file

@ -9,7 +9,7 @@ links: []
# Plan naprawy subsystemu A (control-plane) — 2026-07-28
Kontekst: docs/architecture/RECON-multiagent-2026-07-27.md. Decyzje bazowe:
Kontekst: kb/subsystems/recon-multiagent.md. Decyzje bazowe:
- Flota dzieli się na dwa subsystemy. A = utrzymaniowy (control-plane, node-agenty,
self-healing) — ten plan. B = zleceniowy (dyspozytor + Telegram + KB + HA +
homelab-ops) — prowadzony w osobnym projekcie, poza tym planem.

View file

@ -0,0 +1,60 @@
---
okf: "0.1"
type: runbook
visibility: private
status: active
updated: 2026-05-27
links:
- ../subsystems/control-plane.md
---
# Control-plane — deployment i recovery
## Deployment
### From SATURN (primary control node)
```bash
# Full deploy via SSH
./scripts/deploy/deploy-control-plane.sh --ssh
# Or manually:
ssh oskar@100.95.58.48 "cd ~/homelab-codex-ws && git pull origin master && cd services/control-plane && docker compose up -d --build --force-recreate"
```
### Direct on VPS
```bash
cd ~/homelab-codex-ws/services/control-plane
docker compose up -d --build --force-recreate
```
`deploy-local.sh` also creates the required `/opt/homelab/` directory structure and sets ownership to UID 1000 (requires `sudo`). If directories already exist, skip to the `docker compose` step directly.
### Verification
```bash
# On VPS
docker ps --filter "name=control-plane"
curl -s http://localhost:18180/summary | python3 -m json.tool
```
## Recovery
### World state is stale or corrupt
```bash
# On VPS — delete checkpoint to force full replay
rm /opt/homelab/state/observer_checkpoint.json
docker restart control-plane-observer
```
### Flood of pending actions after bootstrap
Check if node-agent is running and emitting `service_healthy` events on each node. Without `service_healthy`, the supervisor sees all services as missing and queues redeployments every cycle.
```bash
# Check node-agent on each node
ssh oskar@<node> "docker ps --filter name=node-agent && docker logs node-agent --tail 20"
```
### Rebuild from scratch
```bash
ssh oskar@100.95.58.48 "cd ~/homelab-codex-ws/services/control-plane && docker compose up -d --build --force-recreate"
```

View file

@ -12,7 +12,7 @@ links:
## First-time deployment
See **[DEPLOY.md](DEPLOY.md)** for the full procedure: HA token creation,
See **[DEPLOY.md](ha-diag-agent-deploy.md)** for the full procedure: HA token creation,
per-host `.env` config, deploy commands, verification steps, 48h shadow-mode
observation, and rollback.

View file

@ -13,7 +13,7 @@ links:
## Deploy (PIHA)
0. Prerequisite: `ollama-piha` deployed and `bge-m3` pulled — see
`services/ollama-piha/README.md` (the pull is a **manual** deploy step).
`kb/services/ollama-piha.md` (the pull is a **manual** deploy step).
1. `git pull` on PIHA (`~/homelab-codex-ws`).
2. `cp services/kb-query/env.example services/kb-query/.env` and fill in the
real `KB_DSN` password (the template already sets `EMBED_FALLBACK_URL`).

View file

@ -97,7 +97,7 @@ bind-mount the existing model directory.**
docker exec ollama ollama ps
```
3. Embeddings endpoint + vector dimension (deferred check from
`docs/kb/modules/05-faza2-plan.md` §6 step 2):
`kb/phases/kb-m5-faza2.md` §6 step 2):
```bash
curl -s http://localhost:11434/api/embeddings -d '{"model":"bge-m3","prompt":"test"}' \
| python3 -c "import json,sys; v=json.load(sys.stdin)['embedding']; print(len(v))"
@ -145,7 +145,7 @@ SOLARIA:
- Given the missing driver, the cutover proceeded **in CPU-only mode**: the
`deploy.resources` GPU reservation was commented out in
`services/ollama/docker-compose.yml` (commit `f57a01a`), and the driver fix
was filed as a backlog item (see `docs/backlog.md`) blocking the module 5
was filed as a backlog item (see `kb/phases/backlog.md`) blocking the module 5
mail-embedding phase.
- **2026-07-16: driver fixed.** Installed `nvidia-driver-595-open` from the
distro repository — not the old `ppa:graphics-drivers/ppa` (jammy), which

View file

@ -23,7 +23,7 @@ links:
wildcard `*.kapala.org` już pokrywa tę subdomenę, nowy cert niepotrzebny.
Plus vhost w npm@PIHA (HTTPS → `192.168.31.5:8210`, Advanced puste).
6. `docker compose up -d` + `./healthcheck.sh` + testowy login OIDC.
7. Export NFS dla workera (patrz `services/paperless-worker/README.md`) —
7. Export NFS dla workera (patrz `kb/services/paperless-worker.md`) —
dopiero przy module 3.
8. Wpis w `hosts/piha/services.yaml` (dopiero przy deployu — wcześniej
supervisor widziałby drift dla nieistniejącego serwisu).

View file

@ -64,4 +64,4 @@ Weryfikacja po deployu: `./healthcheck.sh` robi test zapisu na mount.
`inventory/topology.yaml` (obecnie SOLARIA ma tam tylko `node-agent`) —
bez tego supervisor/observer nie widzą tego serwisu w desired-state, więc
drift między `hosts/solaria/services.yaml` a rzeczywistością nie jest
wykrywany. Patrz `docs/backlog.md`.
wykrywany. Patrz `kb/phases/backlog.md`.

View file

@ -0,0 +1,31 @@
---
okf: "0.1"
type: runbook
visibility: private
status: active
updated: 2026-05-11
links:
- ../subsystems/service-lifecycle.md
---
# Serwisy — operational recovery
## Operational Recovery
### 1. Container Failure
If a service is unhealthy:
- Check `docker compose logs`.
- Restart: `docker compose restart`.
- Recreate: `docker compose up -d --force-recreate`.
### 2. Node Failure
If a host node fails:
- Services with `owner_node` matching the failed node must be recovered on a backup node or the node must be restored.
- Persistence data must be restored from backups to `/opt/homelab/data/<service>`.
### 3. Dependency Recovery
If a dependency fails:
- Services depending on it might report unhealthy status.
- Recover the dependency first.
- Re-verify dependent services.

View file

@ -1,16 +1,15 @@
---
okf: "0.1"
type: runbook
visibility: private
status: active
updated: 2026-05-17
links:
- ../subsystems/stability-agent-architektura.md
---
# Stability Agent Multi-Node Rollout
## Architecture Summary
The `stability-agent` is a lightweight Python service that monitors node health (disk, Docker containers, Tailscale, MQTT) and publishes state to a central Redis instance running on **PIHA**.
- **Source**: `services/stability-agent`
- **State Path**: `/opt/homelab/state`
- **Events Path**: `/opt/homelab/events`
- **Redis Target**: `100.108.208.3:6379` (PIHA)
## Why UI only showed CHELSTY
Previously, the `stability-agent` had `NODE_NAME` defaulted to `chelsty` and was only deployed there. The Agent System UI materializer on PIHA filters nodes based on the Redis keys `homelab:nodes:<NODE_NAME>`. Without other agents publishing their specific `NODE_NAME`, the UI remained limited to the single active node.
## Deployment
Use the helper script to deploy or generate commands. The script uses explicit Tailscale IPs for remote targets (piha, chelsty, vps) and runs locally for solaria.

View file

@ -5,7 +5,8 @@ visibility: private
status: active
updated: 2026-08-03
stub: true
links: []
links:
- ../subsystems/control-plane.md
---
# control-plane

View file

@ -10,7 +10,7 @@ links:
# ha-mcp — read-only MCP server for Home Assistant
**Status: phase 2a** of `services/home-assistant/DESIGN.md` — own minimal MCP
**Status: phase 2a** of `kb/decisions/ha-configs-as-code.md` — own minimal MCP
server (the alternative, adopting `hass-mcp`, was the other option in that
document's Open questions; operator decision 2026-07-30: build our own).
@ -38,7 +38,7 @@ in this server that changes anything in Home Assistant:
**The write path back into Home Assistant is unchanged and lives elsewhere:**
edit `services/home-assistant/config/<instance>/` in the repo, then
`scripts/ha/deploy.sh <instance>` (drift-abort → `check_config` → write →
verify). See `services/home-assistant/DESIGN.md`, "Sync model" and
verify). See `kb/decisions/ha-configs-as-code.md`, "Sync model" and
"Validation gate". Nothing in this server bypasses that, and nothing in this
server should ever learn to.
@ -140,7 +140,7 @@ down — you then get `live_error` instead of `live`.
## Why `unavailable` is in every result
The 2026-07-23 audit (`services/home-assistant/docs/audyt-automatyzacji-2026-07-23.md`,
The 2026-07-23 audit (`kb/audits/ha-automatyzacje-2026-07-23.md`,
1.21.4) traced ~15 silently broken automations to dead sensors: a condition
on an `unavailable` entity is simply never true, and HA reports no error. So
every entity view here carries an explicit `unavailable: true` plus

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