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>
51 lines
2.9 KiB
Markdown
51 lines
2.9 KiB
Markdown
---
|
|
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.
|
|
|