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