Placement hardening (F-3a-1..4) + enlarge-blocked delivery chain (Task 3a-fix, v0.134.1)

PlaceOffsiteRestore: live target via raw GetStackHDDPath not AppNamespaceRoot (F-3a-1a: no SSD
merge; undeployed refused), placement headroom gate (F-3a-1b), stat pre-pass over all placements
before any copy (F-3a-4: no partial writes), scratch removed on success/kept on failure (F-3a-2).
mapOffsiteRestorePaths refuses the namespace root itself (F-3a-3).
Delivery chain: DefaultEnabledEvents + GetNotificationPrefs append-if-absent migration + settings
checkbox + handler slice; paired with hub v0.55.0 allowlist (no customerMessages entry — raw
dynamic message survives). +8 tests; all 6 controller §10 red-proofs verified.
This commit is contained in:
2026-07-15 07:54:29 +02:00
parent 482d0d98e3
commit 0cfcc42464
9 changed files with 387 additions and 106 deletions
+15 -1
View File
@@ -241,6 +241,7 @@ var DefaultEnabledEvents = []string{
"health_critical",
"expected_backup_missed",
"expected_dbdump_missed",
"offbox_enlarge_blocked", // 3a-fix (warning-class): remote enlargement refused by the quota gate
}
// PendingEvent is an event queued for the next Hub push cycle.
@@ -522,10 +523,23 @@ func (s *Settings) GetNotificationPrefs() *NotificationPrefs {
// Return a copy of the slice
events := make([]string, len(prefs.EnabledEvents))
copy(events, prefs.EnabledEvents)
prefs.EnabledEvents = events
// 3a-fix append-if-absent migration: an existing customer's stored prefs predate
// offbox_enlarge_blocked, so they cannot have deliberately disabled it — surface it enabled so the
// checkbox renders checked and the startup sync (main.go:782) carries it to the hub. Idempotent.
prefs.EnabledEvents = appendIfAbsent(events, "offbox_enlarge_blocked")
return &prefs
}
// appendIfAbsent appends want to list only if it is not already present (idempotent).
func appendIfAbsent(list []string, want string) []string {
for _, e := range list {
if e == want {
return list
}
}
return append(list, want)
}
// SetNotificationPrefs updates notification preferences and saves to disk.
// H17: Deep-copies prefs so caller mutations after the call don't affect stored state.
func (s *Settings) SetNotificationPrefs(prefs *NotificationPrefs) error {