v0.191.2 — a quiet fill check now says so (R-167)
gates / gates (push) Successful in 9s

Earned during v0.191.1's own live validation. After the customer had been
warned, a restart produced ZERO fillwatch lines — equally consistent with
'ran and chose silence' and 'never ran'. Proving the checker was alive
needed a deliberate crossing into the critical band.

For an edge-triggered check the quiet run IS the healthy steady state, so
that ambiguity is permanent rather than rare. Check now logs a per-RUN
summary on every run, counting unreadable separately from healthy so a
drive that has quietly gone unreadable cannot read as 'all fine'.
This commit is contained in:
2026-08-02 23:37:04 +02:00
parent 5adae4dad9
commit 9a3c4855d7
3 changed files with 100 additions and 0 deletions
@@ -318,3 +318,56 @@ func TestVanishedTargetIsForgotten(t *testing.T) {
"re-added drive would resume from a stale band instead of warning afresh")
}
}
// --- The run summary is the POSITIVE OBSERVABLE ---------------------------------------------------
// Standing rule 3, aimed at the one place it bites hardest here: this check is edge-triggered, so the
// HEALTHY STEADY STATE IS A QUIET RUN. Without a per-run summary line, "the checker ran and correctly
// said nothing" and "the checker is dead" produce byte-identical logs, permanently.
//
// This was hit for real while live-validating v0.191.1 on guest 9201: an unchanged band produced zero
// log lines, and proving the checker was alive required a deliberate threshold crossing.
func TestEveryRunLogsAPositiveObservable(t *testing.T) {
var buf strings.Builder
usage := map[string]*Usage{photos.Path: {UsedPercent: 40, AvailGB: 60, TotalGB: 100}}
w := New(filepath.Join(t.TempDir(), "s.json"), log.New(&buf, "", 0),
func() []Target { return []Target{photos} },
func(p string) *Usage { return usage[p] })
if err := w.Check(); err != nil {
t.Fatal(err)
}
out := buf.String()
if !strings.Contains(out, "[fillwatch] checked 1 filesystem(s)") {
t.Fatalf("a HEALTHY run logged no summary — a quiet run is then indistinguishable from a "+
"checker that never ran, and for an edge-triggered check that is the normal state. Got:\n%s", out)
}
if !strings.Contains(out, "all ok") {
t.Fatalf("the summary does not report the bands. Got:\n%s", out)
}
// And a second, equally quiet run must log again — the observable is per RUN, not per change.
buf.Reset()
if err := w.Check(); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "checked 1 filesystem(s)") {
t.Fatalf("the second unchanged run logged nothing. Got:\n%s", buf.String())
}
}
// The summary must distinguish an unreadable filesystem from a healthy one — otherwise a drive that
// has silently gone unreadable for weeks reads as "all fine".
func TestSummaryCountsUnreadableSeparately(t *testing.T) {
var buf strings.Builder
w := New(filepath.Join(t.TempDir(), "s.json"), log.New(&buf, "", 0),
func() []Target { return []Target{photos} },
func(string) *Usage { return nil })
if err := w.Check(); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "checked 0 filesystem(s), 1 unreadable/skipped") {
t.Fatalf("an unreadable filesystem is not distinguished in the summary — it would read as a "+
"healthy check. Got:\n%s", buf.String())
}
}