# felhom.eu — task reports > **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md). --- # REPORT — hub v0.24.0: dispatcher routes `critical` (NAS A2, "Part 0") **Date:** 2026-06-30 **Task:** `TASK.md` Part 0 — the hub dispatcher fix that ships alongside NAS Part A2 (controller work is in the felhom-controller repo, v0.92.0). ## Baseline | | value | |---|---| | Hub before | v0.23.0 | | Hub after | **v0.24.0** — code `0ff1d3c`, manifest bump `87f0794` | | Live image | `felhom-hub:0.24.0` (ArgoCD `felhom` Synced/Healthy) | ## What changed - **`internal/notify/dispatcher.go` `ProcessEvent`:** the severity gate was `!= "warning" && != "error"` — a `critical`-severity event was **silently dropped** (never emailed). Now routes **warning / error / critical** (`severityNotifies`); `info` stays an intentional non-notify; an **unrecognized** severity is **logged** (`[WARN] Dispatcher: unrecognized severity …`), never silently dropped. - **`internal/monitor/host_disk.go`:** `host_disk_critical` now emits its **natural `critical` severity** (was forced to `error` to survive the old gate); `FormatOperatorEmail` styles `critical` 🔴 like `error`. - **Latent crash guard:** `processCustomer` dereferenced `GetNotificationPrefs`, which returns `(nil, nil)` for a customer with no notification row — an event for such a customer would have **panicked the dispatcher goroutine and crashed the hub**. Now guards `prefs == nil`. - **Seam:** `sendEmailFn` so routing is unit-tested without real HTTP. - Verified safe first (§9 rule 5): grep confirmed **no** event currently emits `critical` (all controller events are info/warning/error; the hub's only would-be `critical` emitter is `host_disk`) → no surprise alert volume. ## Tests — `go build ./... && go vet ./... && go test ./...` GREEN `severityNotifies` (warning/error/critical notify; info/unknown don't) + **companion red-proof** (the pre-fix `warning||error` predicate drops `critical`); ProcessEvent routes `critical` to the operator; an unknown severity is logged not dropped; `info` is silent and not mis-logged. ## Deploy + validation Deployed via the GitOps manifest path (image build+push; `manifests/hub.yaml` 0.23.0→0.24.0; ArgoCD hard-refresh → OutOfSync → sync → **Synced/Healthy**), per the repo convention (not bare `kubectl set image`). Startup clean; the `host_disk` checker re-seeded `1 ok` (the demo host root is at 24% after the prior task's prune). The critical-routing fix is unit-validated; not re-fired live (the demo disk is below threshold). The downstream A2 NAS live e2e (add → jellyfin reads → kill → unreachable warning, no cascade → recover → remove) was validated against an isolated sim NAS — see `felhom-controller/REPORT.md`. ## Observations / follow-ups - Per-storage `StorageTargets` worst-fill alerting remains the easy follow-on to `host_disk`. - Part B (restic-over-SFTP NAS backup target) still pending — separate task. No secrets committed. Operator email address is the operator's own; the Resend key stays injected from `Secret/resend-api`.