Files
felhom-controller/REPORT.md
T
admin 2d20859858 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.
2026-07-14 22:51:54 +02:00

99 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REPORT — Offsite tier policy engine (Task 3a) — controller v0.134.0
## Summary
Task 3a of the backup-classification-redesign arc — the **first behavior-changing** task. Each
offsite-toggled app's push becomes ONE multi-path restic snapshot (recovery unit + the app's
**mandatory** userdata from Task 3-core `ComputeCaptureSet`), with SP-proven quota accounting
(`--mode raw-data`), retention grouping (`--group-by host,tags`), a pre-push enlargement gate
(block + notify; unit-only push continues), and a reworked restore side (scratch off the rootfs with
a headroom gate, unit-only default via `--include`, size-first full restore, and a place-to-live
missing-only merge). Implements `felhom.eu/documentation/architecture/07-backup-architecture.md`
§2/§6/§7/§9 verbatim.
## Baselines (live-verified at session start)
| Repo | `main` @ start | Version | This task |
|---|---|---|---|
| felhom-controller | `0c6e151` | v0.133.0 → **v0.134.0** | backup engine + settings + web + main.go |
| felhom.eu | `8d85da7` | — | reference only (not committed) |
## Files created / modified
- **new** `internal/backup/offbox_capture.go``offboxCaptureSet` (TierOffsite resolution + loud gaps), `offboxBlocked`.
- **new** `internal/backup/offbox_restore.go` — snapshot introspection (ID-first), scratch relocation + headroom, `RestoreOffboxScratch`, `mapOffsiteRestorePaths` (pure) + `PlaceOffsiteRestore`, `OffboxRestorePrepareFull`, `OffboxFullScratchReady`, free-space seam.
- **new** `internal/backup/offbox_3a_test.go` — 13 tests (Scenarios AG + extras).
- **mod** `internal/backup/offbox.go` — multi-path argv + gate in `runOffboxInternal`; raw-data stats; `--group-by host,tags` on both forget sites; warns/EnlargedBlocked/edge-notify in `RunOffboxBackup`; setters + sizer.
- **mod** `internal/backup/backup.go` — 4 new Manager seams (sizer, enlarge-blocked notifier, place copier, free-fn).
- **mod** `internal/settings/settings.go``OffboxTarget.EnlargedBlocked []string`.
- **mod** `internal/notify/notifier.go``NotifyOffboxEnlargeBlocked` (event `offbox_enlarge_blocked`, warning).
- **mod** `cmd/controller/main.go` — enlarge-blocked notifier wiring (+ `appbackup` import for HumanizeBytes).
- **mod** `internal/web/offbox_handlers.go` — restore two-step (mode unit/full) + `offboxPlaceHandler`; `EnlargedBlocked`/`RepoSizeBytes` preserved across a config edit.
- **mod** `internal/web/server.go` — route `POST /backup/offbox/place`.
- **mod** `internal/web/handlers.go` — restore-page reveal params + scratch-ready map; blocked-set map for the remote page.
- **mod** `internal/web/templates/backups_restore.html`, `backups_remote.html` — Hungarian actions + notes (nested-`if` guards so render tests without the new keys don't crash).
- **mod** CHANGELOG / REPORT / CONTEXT / README.
Untouched by design: `RunTier2`/tier-2 (3b), `.fab`/appexport (Task 4), the hub, the agent, `discoverOffboxUnit`, escrow gates, the ≥100% run-refusal.
## Tests — results
`go build ./... && go vet ./... && go test ./...`**all green.** +13 new tests (`offbox_3a_test.go`);
existing offbox/web/settings suites unchanged and green.
### §10 red-proofs (mutation → FAIL → revert), every one verified
| ID | Mutation | Test that failed |
|---|---|---|
| A | tier filter admits optional to offsite | `EnlargedPush_MandatoryOnly` (optional `:ro` in argv) |
| B | force classified handling for a legacy app | `LegacyUnitOnly` (resolved `appdata/sonarr` in argv — SQ5 regression) |
| C | delete the enlargement gate | `EnlargementGateBlocks` (immich enlarged + EnlargedBlocked empty) |
| D | drop the stat-filter | `CaptureGapsAreLoud` (missing `ghost` present in argv) |
| E | revert stats to modeless | `StatsRawDataMode` (no `--mode raw-data`) |
| F-main | drop `--group-by` from `runOffboxInternal` forget | `ForgetGrouping_MainRun` |
| F-prune | drop `--group-by` from `offboxPruneOnly` forget | `ForgetGrouping_OverQuotaPrune` |
| G-anchor | break the unit-path anchor trim | `MapOffsiteRestorePaths` (wrong dest) |
| G-escape | remove the namespace-escape refusal | `MapOffsiteRestorePaths` (out-of-ns path accepted) |
| Headroom | proceed on size-unknown | `FullRestoreRefusesOnSizeUnknown` (restore attempted) |
All reverted; post-revert full suite green; no `RP-` residue in source (one explanatory test comment only).
## Deploy / verify
Built + pushed `felhom-controller:0.134.0` on 180; deployed to demo guest 9201. Per-guest `docker ps`
+ startup log + the §13 live-validation evidence recorded below.
_Deploy + live-validation evidence appended after the live run — see the deploy commit._
## Hub flag (rule 9.2 — flagged, not made)
The enlarge-blocked **push notification** needs the hub to add `offbox_enlarge_blocked` to
`allowedEventTypes` + `customerMessages` (a hub-side task). Until then the hub 400s/drops the event
and the message reaches the customer only in-dashboard (the `LastWarning` line + the /backups/remote
per-app note, both live). No existing event type fit a "warning, not failure" semantics, and adding a
hub event type is out of this task's scope.
## NOT yet live-validated — awaiting CAMPAIGN-6D (supervised)
- `PlaceOffsiteRestore` against live data (unit-tested only; STOP boundary).
- A full restore of a large set (unit-tested; the demo apps are small).
- The enlarge-blocked path firing live (demo quota is 50 GB — won't trip; unit-tested).
- Notification delivery end-to-end (blocked on the hub flag above).
- The SQ3 immich offsite-only full-circle restore.
## Observations (documented, NOT acted on)
- **Stale feature doc:** `felhom.eu/documentation/controller/backup-architecture.md` (v0.59.0-era)
states "Restic is gone from the controller" — already false before 3a (offbox restic-SFTP has run
since ~v0.68). It is globally stale about the offsite leg; a targeted 3a patch would be incoherent
beside that claim. The authoritative offsite home is `architecture/07-backup-architecture.md`
(aligned in Task 3-core). A full rewrite of the controller feature doc is its own task.
- **offsite capture resolution uses the raw `GetStackHDDPath`**, not `GetAppDrivePath` — the latter's
`systemDataPath` fallback would resolve userdata onto the SSD (wrong drive). Empty HDD ⇒ undeployed
⇒ unit-only + WARN (§2.4). This is a deliberate deviation from the §5 reuse-table hint, driven by
the §8 edge-table semantics.
- `offboxRecordStats` still reuses one probe-timeout context for both `snapshots` and `stats`
(pre-existing, noted in §12 of the architecture doc) — untouched here.
- Free-space probe uses `df` via an injectable seam (`SetOffboxFreeFn`) so tests run on the Windows
host where `df` is absent; production uses the real `df` on the Linux guest.