From ba22f9b2d96711684eba35ebd369f2fdd4fad204 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Wed, 21 Oct 2026 23:40:21 +0200 Subject: [PATCH] R-1199: a box whose household never saved its notification choice pushes the default list its page shows at start, so the hub mails by it; unreleased --- CHANGELOG.md | 8 ++++ controller/cmd/controller/main.go | 4 +- .../cmd/controller/prefs_sync_wiring.go | 30 +++++++++++++++ .../cmd/controller/prefs_sync_wiring_test.go | 38 +++++++++++++++++++ controller/internal/settings/settings.go | 9 +++++ 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 controller/cmd/controller/prefs_sync_wiring.go create mode 100644 controller/cmd/controller/prefs_sync_wiring_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 163e257..1a0f0aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,14 @@ line; the controller re-creates its definition while sharing is on. Any other refused share is still named. `TestW28_TheSystemDropZoneIsNotNamedAsRefused`. +- **R-1199 — a box whose household never saved its notification choice now tells the hub the list its page shows.** + Measured the night of 2026-10-21 (felhom.eu `audits/night-2026-10-21/P5-checks/`): the box's page showed the + default list ticked (disk warning, drive disconnected, second-drive failure …) but pushed nothing until the household + saved the page, and the hub mails the household only by its own stored list — for Tester4 a list saved 2026-10-18, + two days before the rehearsal box existed, without those three; the household got one alarm of nine. Now the + start-up sync also runs when no choice was ever saved, with the default list and no address (the hub's no-clobber + guard keeps the stored address). A saved choice is unchanged (R-922). *Decided by CC unattended — the box's page is + the source, following `08`'s „the household decides"; operator may reverse.* `TestR1199_*` (red-proved). ## v0.337.0 (2026-10-21) — kept second-drive copies (D25: R-1175, R-1181, R-1179); a `.fab` import keeps the family gate (R-1191); a stale restore temp no longer blocks the retry (R-1188 c); the overcommit sentence made true (R-1192); escrow recorded before the floor (R-1176); a finished off-site abandonment closes on the hub's word (R-1182); storage labels follow the language (W26) **MinAgent: 0.158.1** — the floor's, carried; no agent change is needed by this release (the R-1192 swap fix is the diff --git a/controller/cmd/controller/main.go b/controller/cmd/controller/main.go index 1acc515..a083265 100644 --- a/controller/cmd/controller/main.go +++ b/controller/cmd/controller/main.go @@ -1891,7 +1891,9 @@ func main() { go func() { prefs := sett.GetNotificationPrefs() // R-922: a household's clear is pushed too, so a clear the hub missed reaches it at the next start. - if prefs.SyncOnStartup() { + // R-1199: a box whose household never saved its choice pushes the default list its page shows (with no + // address — the hub keeps the stored one, its no-clobber guard). + if prefsSyncAtStartup(prefs, sett.NotificationPrefsSaved(), logger) { if err := notifier.SyncPreferences(prefs.Email, prefs.EnabledEvents, prefs.CooldownHours, prefs.EmailCleared); err != nil { logger.Printf("[WARN] Failed to sync notification preferences on startup: %v", err) } diff --git a/controller/cmd/controller/prefs_sync_wiring.go b/controller/cmd/controller/prefs_sync_wiring.go new file mode 100644 index 0000000..ab18159 --- /dev/null +++ b/controller/cmd/controller/prefs_sync_wiring.go @@ -0,0 +1,30 @@ +package main + +import ( + "log" + + "gitea.dooplex.hu/admin/felhom-controller/internal/settings" +) + +// R-1199 (rehearsal 2026-10-20; mechanism read the night of 2026-10-21, `audits/night-2026-10-21/P5-checks/`): a fresh +// box shows the household its DEFAULT notification list ticked (disk warning, drive disconnected, second-drive failure +// …), but pushed nothing to the hub until the household saved the page — and the hub mails the household only by its +// OWN stored list (`processCustomer`), which for Tester4 was a list saved two days before that box existed. So the +// household got one alarm of nine. The box's page is the household's choice (`08` „the household decides" — the three +// groups); the hub follows it. Decided by CC unattended — operator may reverse. +// +// prefsSyncAtStartup: push at start when the household saved an address or a clear (R-922, unchanged), or when it never +// saved a choice at all (the default list, pushed with no address — the hub's no-clobber guard keeps the stored one). +// Pinned by TestR1199_*. +func prefsSyncAtStartup(prefs *settings.NotificationPrefs, saved bool, logger *log.Logger) bool { + if prefs.SyncOnStartup() { + return true + } + if !saved && prefs != nil { + if logger != nil { + logger.Printf("[INFO] notification prefs: the household never saved its choice — pushing the default list the page shows, so the hub mails by it (R-1199)") + } + return true + } + return false +} diff --git a/controller/cmd/controller/prefs_sync_wiring_test.go b/controller/cmd/controller/prefs_sync_wiring_test.go new file mode 100644 index 0000000..01bca04 --- /dev/null +++ b/controller/cmd/controller/prefs_sync_wiring_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "testing" + + "gitea.dooplex.hu/admin/felhom-controller/internal/settings" +) + +// R-1199: a box whose household never saved its notification choice pushes the default list at start; a saved choice +// without an address (and no clear) is still not pushed (R-922's rule unchanged); a saved address is pushed. +// RED-PROOF: with the `!saved` branch removed the first case fails — the hub keeps mailing by a list the page never showed. +func TestR1199_ANeverSavedChoiceIsPushedAtStart(t *testing.T) { + def := &settings.NotificationPrefs{EnabledEvents: settings.DefaultEnabledEvents, CooldownHours: 6} + if !prefsSyncAtStartup(def, false, nil) { + t.Fatal("a box with no saved choice must push the default list its page shows") + } + if prefsSyncAtStartup(&settings.NotificationPrefs{EnabledEvents: []string{"backup_failed"}}, true, nil) { + t.Fatal("a saved choice with no address and no clear stays local (R-922 unchanged)") + } + if !prefsSyncAtStartup(&settings.NotificationPrefs{Email: "x@example.invalid", EnabledEvents: []string{"backup_failed"}}, true, nil) { + t.Fatal("a saved address is pushed, as before") + } +} + +// The default list the page shows carries the three events the rehearsal household did not get (the consequence). +func TestR1199_TheDefaultListHoldsTheAlarmsTheHouseholdMissed(t *testing.T) { + want := map[string]bool{"disk_warning": false, "storage_disconnected": false, "crossdrive_failed": false} + for _, e := range settings.DefaultEnabledEvents { + if _, ok := want[e]; ok { + want[e] = true + } + } + for e, ok := range want { + if !ok { + t.Fatalf("%s is not in the default list", e) + } + } +} diff --git a/controller/internal/settings/settings.go b/controller/internal/settings/settings.go index 82b21c1..bd2b88f 100644 --- a/controller/internal/settings/settings.go +++ b/controller/internal/settings/settings.go @@ -676,6 +676,15 @@ func (p *NotificationPrefs) SyncOnStartup() bool { return p != nil && (p.Email != "" || p.EmailCleared) } +// NotificationPrefsSaved reports whether the household ever saved its notification choice on this box. A box that never +// did shows DefaultEnabledEvents ticked on its page (GetNotificationPrefs) — R-1199: the hub must then mail by that +// same list, so the start-up sync pushes it (main.go, prefsSyncAtStartup). +func (s *Settings) NotificationPrefsSaved() bool { + s.mu.RLock() + defer s.mu.RUnlock() + return s.Notifications != nil +} + // DefaultEnabledEvents are the events enabled by default for new customers. var DefaultEnabledEvents = []string{ "backup_failed",