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
+30
View File
@@ -1,5 +1,35 @@
## Changelog
### v0.134.1 — Placement hardening (F-3a-1..4) + enlarge-blocked notification delivery chain (Task 3a-fix) (2026-07-15)
Follow-up hardening of the (not-yet-live) place-to-live flow from v0.134.0, plus the controller side
of the `offbox_enlarge_blocked` notification delivery chain (paired with hub v0.55.0). No new
architecture.
- **F-3a-1a (`offbox_restore.go` `PlaceOffsiteRestore`):** the live target now resolves via the RAW
`GetStackHDDPath` (mirrors `offboxCaptureSet`), NOT `AppNamespaceRoot` whose `systemDataPath`
fallback would have merged userdata onto the SSD system namespace. Empty HDD ⇒ undeployed ⇒ refuse
(`"a(z) %s nincs telepítve — előbb állítsd helyre az alkalmazást, utána az adatokat"`).
- **F-3a-1b:** placement headroom gate — refuse before any copy if `offboxFree(liveNs) <
offboxSize(scratch)` (a missing-only merge copies at most the scratch size).
- **F-3a-4:** the src-existence check is now a `os.Stat` PRE-PASS over EVERY placement before the
first copy — an incomplete scratch (e.g. a unit-only restore) refuses with ZERO copies, making the
"no partial writes" guarantee true (was interleaved: the unit could be placed before the refusal).
- **F-3a-3 (`mapOffsiteRestorePaths`):** the escape check tightened to `!HasPrefix(p, oldNs+"/")` so
the namespace root itself (`p == oldNs`) is refused instead of mapping to a junk placement.
- **F-3a-2:** on FULL success the scratch is removed best-effort (logged); `OffboxFullScratchReady`
then turns false so the place button disappears. A FAILED placement keeps the scratch for a retry.
- **Delivery chain (controller side):** `settings.DefaultEnabledEvents` gains
`offbox_enlarge_blocked`; `GetNotificationPrefs` append-if-absent migration surfaces it enabled for
existing customers (idempotent — a customer couldn't have disabled a type that didn't exist), which
the startup prefs sync (`main.go:782`) carries to the hub; the settings-page checkbox
(`"Távoli mentés — tárhelykeret-figyelmeztetés"`) + the handler's single-event slice both gain it
(missing either half re-opens the checkbox-drop trap). Hub v0.55.0 allowlists the event; NO
`customerMessages` entry (the raw dynamic two-number message must survive — `templates.go:129`).
- **Tests:** +5 placement (`offbox_place_test.go`: undeployed/headroom/incomplete-pre-pass/lifecycle
+ mapping namespace-root) + 3 settings (`notif_migration_test.go`: default-contains, migration
idempotency, no-duplicate). All 6 controller §10 red-proofs verified (mutate → fail → revert).
### v0.134.0 — Offsite tier policy engine: mandatory userdata, raw-data quota, restore rework (Task 3a) (2026-07-14)
Task 3a of the backup-classification-redesign arc — the FIRST behavior-changing task