hub v0.90.1 — the digest's per-app lines stop repeating the filesystem figures (R-182)
gates / gates (push) Successful in 7s

Found by reading the first REAL digest, not by design. Every app row ended with
the same usage clause the mail already prints once on its own Filesystem line.
On a two-app box that is untidy; down a list of a dozen it is the same forty
characters twelve times, pushing the part that DIFFERS off a phone screen at
07:00 — the only moment this mail has to work.

The reserve's refusal message is authored for a single-app alert where naming
the filesystem is right, so the message is unchanged; the digest trims the
duplicate when rendering. trimRepeatedUsage removes ONLY an exact
"— <target path>:" suffix, so an unrelated reason is untouched and a reason that
is nothing but the usage clause is left alone rather than emptied.

Also updates TestRecoveryUnitCaptureFailed_NeverReachesTheCustomer, which
required the OPERATOR to be emailed a per-app capture failure. That was correct
when the event was the only signal and is wrong now that it is the record and
the digest is the notification. Its customer-safety claim is unchanged and is
why the test still exists; the operator assertion is inverted with the reasoning
written in place, and R-158's guarantee is shown to have MOVED, not weakened.
This commit is contained in:
2026-08-03 13:54:02 +02:00
parent dd40f85bb8
commit f21e7caed1
4 changed files with 85 additions and 14 deletions
@@ -39,33 +39,45 @@ func TestRecoveryUnitCaptureFailed_NeverReachesTheCustomer(t *testing.T) {
}
}
// The operator must still get it: the register mutes the customer channel, not the signal.
gotOperator := false
// R-182 CHANGED WHAT THIS ASSERTS, DELIBERATELY, AND THE OLD ASSERTION IS WORTH KEEPING IN VIEW.
//
// Until 2026-08-03 this test required the OPERATOR to be e-mailed here, on the grounds that "the
// alert is the whole point of R-158". That was right when this event was the only signal, and it
// is wrong now: measured, nine of these arrived at the hub and two were mailed, because the
// operator cooldown key carries no app identifier — so as an alarm it told the operator about one
// app and threw the rest away.
//
// The type is now RECORD-ONLY: written down every time, never mailed. R-158's guarantee — the
// operator learns WHICH app failed and WHY — is not weakened, it MOVED: the per-run digest
// `backup_run_failures` carries every failed app in one mail, and is pinned by
// backup_run_digest_test.go. The customer safety claim below is untouched and is the reason this
// test still exists.
for _, to := range rec.to {
if to == "operator@felhom.eu" {
gotOperator = true
t.Fatal("the operator was e-mailed a PER-APP capture failure — this type is the record " +
"now, not the alarm. One mail per failing app on a full disk is a dozen mails, which " +
"is the volume problem the operator ruled against; the digest is the notification")
}
}
if !gotOperator {
t.Fatal("the operator was not notified of a recovery-unit capture failure — the alert is the " +
"whole point of R-158 and it went nowhere")
}
// The skip must be VISIBLE. An absent log row is equally consistent with "correctly skipped" and
// "the dispatcher never ran" — the positive observable is the row itself (standing rule 3).
// The RECORD must exist, always. It is what makes the digest trustworthy: if the digest is ever
// lost, delayed or suppressed, the failures are still individually written down. An absent row is
// equally consistent with "correctly not mailed" and "the dispatcher never ran" — the positive
// observable is the row itself (standing rule 3).
logs, err := st.GetRecentNotifications("c1", 20)
if err != nil {
t.Fatalf("GetRecentNotifications: %v", err)
}
found := false
for _, l := range logs {
if l.Channel == "customer" && l.Status == "skipped" && strings.Contains(l.ErrorMessage, "operator_only") {
if l.EventType == "recovery_unit_capture_failed" && l.Status == "recorded" &&
strings.Contains(l.ErrorMessage, "record-only") {
found = true
}
}
if !found {
t.Fatalf("the customer skip is not logged as skipped/operator_only — it is indistinguishable "+
"from a delivery that never happened; got %d row(s)", len(logs))
t.Fatalf("the per-app failure left no 'recorded' row — a failure that is neither mailed nor "+
"written down is exactly the 2026-08-03 defect, rebuilt; got %d row(s)", len(logs))
}
}