homelab-codex-ws/kb/incidents/2026-07-12-observer-checkpoint-leksykalny.md
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

2.9 KiB

okf type visibility status updated links
0.1 incident private active 2026-08-03
../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.