Offsite tier policy engine: mandatory userdata, raw-data quota, restore rework (Task 3a, v0.134.0)
Each toggled app's offsite push = one multi-path restic snapshot (recovery unit + TierOffsite mandatory userdata via ComputeCaptureSet); legacy/undeployed stay unit-only. Loud capture gaps (SP-3.4: restic 0.14.0 silently skips missing paths). Quota = stats --mode raw-data (SP-1; displayed size drops once). Pre-push enlargement gate blocks the userdata enlargement over-quota (unit-only push continues; EnlargedBlocked; edge-triggered notify). forget --group-by host,tags on both sites (SP-2). Restore reworked: scratch off the rootfs + headroom gate (F-A1), unit-only default via --include, size-first full, place-to-live missing-only merge (never --delete). UI: unit/full-two-step/place actions + per-app blocked note; route POST /backup/offbox/place. HUB FLAG: offbox_enlarge_blocked event needs hub allowlist for push delivery. +13 tests; all 10 §10 red-proofs verified. No tier-2/.fab/hub/agent changes.
This commit is contained in:
@@ -1,5 +1,55 @@
|
||||
## Changelog
|
||||
|
||||
### 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
|
||||
(`felhom.eu/documentation/architecture/07-backup-architecture.md` §2/§6/§7/§9; restic mechanisms
|
||||
proven in `SPIKE-restic-snapshot-shape-2026-07-14.md`). Offsite pushes now carry each app's
|
||||
**mandatory** userdata, quota is measured as real Storage Box fill, retention survives the shape
|
||||
change, and restore is reworked off the rootfs. **BEHAVIOR CHANGE.**
|
||||
|
||||
- **Multi-path snapshot (§6):** each toggled app's offsite push is now ONE restic snapshot =
|
||||
recovery unit + the app's TierOffsite mandatory capture set (Task 3-core `ComputeCaptureSet`).
|
||||
Optional/excluded never ship offsite. Legacy (no block) / undeployed apps stay **unit-only**,
|
||||
byte-identical to v0.133.0 (the SQ5 cost guard). New `offbox_capture.go`.
|
||||
- **Loud capture gaps (SP-3.4):** restic 0.14.0 does NOT error on a missing source path (exit 0,
|
||||
silent partial snapshot), so a structurally-refused or on-disk-missing MANDATORY path is detected
|
||||
BEFORE invocation (guard `Skipped` list + `os.Stat` filter) and surfaced in the English log **and**
|
||||
the Hungarian `LastWarning`. A restic exit code never proves a path was captured.
|
||||
- **Quota = raw-data (§9, SP-1):** `offboxRecordStats` now runs `stats --mode raw-data --json`
|
||||
(actual deduplicated+compressed repo bytes) instead of the modeless restore-size that multiplied
|
||||
by the retained-snapshot count. **The displayed remote-backup size drops once after deploy** — it
|
||||
now reflects the customer's true Storage Box fill.
|
||||
- **Pre-push enlargement gate (§9, ruling #1):** before an app's enlarged push, if last-known
|
||||
raw-data repo bytes + the mandatory-set `du` estimate would cross the soft quota, the ENLARGEMENT
|
||||
is blocked (config+DB unit-only push still proceeds — never a protection regression), the app is
|
||||
recorded in `OffboxTarget.EnlargedBlocked`, `LastWarning` names it, and an **edge-triggered**
|
||||
notification fires once per new block (`offbox_enlarge_blocked` event, warning severity). A
|
||||
per-app "config+DB only" note renders on /backups/remote.
|
||||
- **Retention grouping (§6, SP-2):** both `forget` call sites gain `--group-by host,tags` so an
|
||||
app's old unit-only-shape snapshots share a group with its enlarged shape and age out naturally
|
||||
(the default host,paths grouping would strand old-shape snapshots in a permanently-retained group).
|
||||
- **Restore rework (§7, F-A1):** new `offbox_restore.go`. Scratch moves off the ~8 GB guest rootfs
|
||||
to a data drive (`<nsRoot>/backups/offsite-restore/<app>`) behind a headroom gate (full needs
|
||||
size×1.1, unit-only a 2 GiB floor; ID-first `snapshots latest --tag` → `stats <ID>`; size-unknown
|
||||
fails closed). `RestoreOffboxScratch(full)` — unit-only DEFAULT via `--include <absolute-unit-path>`
|
||||
(SP-3.2), full is a size-first two-step. `PlaceOffsiteRestore` places a completed full scratch into
|
||||
live locations via a missing-only merge (`rsync -a --ignore-existing`, never `--delete`), unit only
|
||||
if the live unit is absent; the pure `mapOffsiteRestorePaths` refuses the whole placement on no-unit
|
||||
/ escape / reserved-zone. Legacy rootfs scratch is cleaned best-effort. The old `RestoreOffbox`
|
||||
(whole-snapshot to an explicit dest) is retained for existing callers.
|
||||
- **UI (Hungarian):** /backups/restore offers unit-only ("Visszaállítás ellenőrzéshez"), full
|
||||
two-step ("Teljes visszaállítás előkészítése" → "…indítása (~méret)"), and place-to-live
|
||||
("Helyreállítás az élő adatok közé (csak a hiányzó fájlok)"); /backups/remote shows the per-app
|
||||
quota-blocked note. New route `POST /backup/offbox/place`.
|
||||
- **Settings:** `OffboxTarget.EnlargedBlocked []string` (replaced each OK run; preserved across a
|
||||
config edit). **HUB FLAG:** the `offbox_enlarge_blocked` event needs adding to the hub's
|
||||
`allowedEventTypes` + `customerMessages` for delivery — until then the in-dashboard `LastWarning`
|
||||
and the /backups/remote note carry the message (see REPORT §flags).
|
||||
- **Tests:** +13 in `internal/backup/offbox_3a_test.go` (Scenarios A–G + all-excluded, raw-data,
|
||||
both forget sites, restore argv, size-unknown refusal, scratch cleanup, place-to-live mapping);
|
||||
all 10 §10 red-proofs verified (mutation → fail → revert). No tier-2 / .fab / hub / agent changes.
|
||||
|
||||
### v0.133.0 — Capture-set computation (INERT; Task 3-core) (2026-07-14)
|
||||
|
||||
Task 3-core of the backup-classification-redesign arc
|
||||
|
||||
Reference in New Issue
Block a user