docs(v0.215.0): CHANGELOG, CONTEXT decisions, README feature, REUSE entries
gates / gates (push) Successful in 10s

- CHANGELOG leads with the severity fix and the live warning-vs-warn proof.
- CONTEXT records the settled decisions so they are not re-litigated: Hiba is
  the label for predicted failure (no fourth word); sustain before count and
  why; the provenance of 64/55/60; phase 2 owns the new SMART attributes
  because they are a wire change under G-1; phase 1 state is one record per
  disk, not a series.
- README documents the 14-row ladder, the persisted state, the hourly cadence
  and the five message shapes.
- REUSE pins the severity wire contract on PushEvent — the defect's real home,
  so the next typo'd severity is caught at the table rather than in production
  — and records priorFor vs cardPriorFor, which differ by one observation and
  make the chip disagree with the email if mixed up.
This commit is contained in:
2026-08-14 08:34:31 +02:00
parent 34d83f5a02
commit 8144a70a72
4 changed files with 181 additions and 14 deletions
+40 -10
View File
@@ -261,21 +261,51 @@ backups, monitoring and notifications. All Proxmox/disk operations are delegated
## Features
### Disk health — "Lemezek állapota" card + degradation alert (v0.169.0)
### Disk health — "Lemezek állapota" card + degradation alert (v0.169.0; severity ladder v0.215.0)
The dashboard shows a per-physical-disk health card driven by the agent's SMART summary (serialized
into `/disks` from agent v0.94.0 — no new smartctl load; the controller only reads it). One pure
verdict function (`agentapi.DiskVerdictFor`) is the shared truth for the card chip and the check:
verdict function (`agentapi.DiskVerdictFor`) is the shared truth for the card chip and the check.
**Why it does not trust `smart_status.passed`:** attributes 187/197/198 all carry `thresh: 0` and a
normalized SMART value floors at 1, so a drive's own overall verdict is **structurally incapable** of
failing on unreadable sectors — the real failing drive read `PASSED` at 352 pending sectors. The ladder
reads the raw counters instead. Evidence:
`felhom.eu/documentation/audits/DIAG-smart-passed-trap-2026-08-14.md`.
**Four labels, and there will not be a fifth** — a predicted failure is „Hiba", the same word a
self-reported failure gets:
- **Rendben** — clean.
- **Figyelmeztetés** — a first sighting of unreadable sectors below the bar, reallocated sectors, NVMe
media errors, `percentage_used ≥ 90`, or `temperature_c ≥ 55`.
- **Hiba** — the drive reports FAILING, **or** (v0.215.0) unreadable sectors are **sustained** across
two consecutive checks, unreadable sectors sit alongside reallocation, the count reaches **64**,
`temperature_c ≥ 60`, NVMe's `critical_warning` is set, or `percentage_used ≥ 100`.
- **Nincs adat** — nil/UNKNOWN/old agent. Never alarms, and never erases a disk's stored history.
`DiskVerdictFor(smart, prior)` takes an `agentapi.DiskPrior`; a zero prior is the fail-safe, so a first
sighting can only reach Figyelmeztetés. The full 14-row truth table, evaluated top-down, is documented
in `internal/agentapi/diskverdict.go` together with the provenance of 64 / 55 / 60.
- **Rendben** (PASSED, clean) · **Figyelmeztetés** (PASSED but reallocated/pending/offline-uncorrectable
or NVMe critical/media/percentage-used ≥ 90) · **Hiba** (FAILING) · **Nincs adat** (nil/UNKNOWN/old
agent — never alarms).
- The card fetches `/disks` through a **60 s TTL cache** (dashboard refresh-spam can't smartctl-storm
the host); an unreachable agent renders "Nincs adat" and the page still loads.
- A **6-hourly `disk-health-check`** emits `disk_health_degraded` (warn/critical) **only on a
degradation** vs an in-memory baseline — first run baselines silently, recovery/UNKNOWN never notify,
and a controller restart re-baselines silently. **No global banner** (deliberate): the card + email
carry it. The hub allowlist must include `disk_health_degraded`.
the host); an unreachable agent renders "Nincs adat" and the page still loads. The card replays the
prior that produced the stored verdict (`diskRecord.PriorSawUncorrectable`) so the chip and the email
cannot disagree.
- An **hourly `disk-health-check`** (v0.215.0; was 6-hourly — the real drive's benign excursion lasted
about one hour, and `/disks` measures ~0.82 s) emits `disk_health_degraded` against **persisted**
per-disk state in `disk-health-state.json` under `cfg.Paths.DataDir`. It fires on an escalation
against the **last alerted** verdict, and re-alerts a disk already at Hiba once it has **both**
doubled its unreadable-sector count **and** waited out a 24 h cooldown. A disk's first verdict
baselines silently; recovery and UNKNOWN never notify; a disappeared disk is forgotten. A restart no
longer re-baselines — that is why the state is persisted. Every cycle logs a positive observable
(`disk-health check complete: N disk(s) evaluated, M alert(s)`).
- **Severity is a wire contract:** the alert emits `warning` / `critical` — the hub coerces anything
outside `{info, warning, error, critical}` to `info` and never emails it. `DiskAlertKind.Severity()`
is the single mapping. The hub allowlist must include `disk_health_degraded`.
- Five message shapes (`notify.DiskAlert`), because the customer's action differs: Figyelmeztetés,
drive-reported FAILING, Hiba-from-sector-count, Hiba-from-heat, and the still-worsening re-alert.
- **No global banner** (deliberate): the card + email carry it.
### 1. App Management