tier2_capture.go: classified apps get TierSecondary per-bind legs (paperless copy shrinks — export
drops); legacy apps keep the byte-identical resolver set. v2 relpath-mirroring layout
(backups/secondary/<stack>/{marker LAST, recovery-unit/, hdd/<rel>/, userdata/<rel>/}); N>1 native
(errTier2MultiDir/tier2AppDataName deleted). Migration=delete-and-rebuild + reconcile; all RemoveAll
via tier2SafeRemove (refuses outside backups/secondary/). SSD=state-only tier. selectTier2Target
never picks network storage (pinned+auto, F-6C-1). Restore reads v2 behind a marker gate.
Part 0: offbox_enlarge_blocked is a persisted one-time Load seed (opt-out sticks), not a getter
append. Part 0.5: offsite restore scratch prefers a local (non-network) path.
Full v2 test suite + all 10 §10 red-proofs verified. Destructive writes bounded to backups/secondary/.
4.4 KiB
REPORT — Tier-2 engine rework (Task 3b) — controller v0.135.0
Summary
Task 3b: the tier-2 column of the matrix (architecture §2/§8). The tier-2 appdata leg becomes
class-driven for classified apps (per-bind mandatory + optional; paperless's copy shrinks as
export drops out), legacy apps keep a byte-identical resolver set — both mapped into a new v2
relpath-mirroring layout that lifts the N>1 refusal structurally and gains a reconcile step; the SSD
fallback is an enforced state-only tier; and selectTier2Target gains the F-6C-1 network-target
exclusion (pinned + auto). Restore reads v2 behind a marker gate. Plus Part 0 (persisted prefs
seed — fixes the 3a-fix un-disableable checkbox) and Part 0.5 (offsite scratch prefers local).
Every destructive write lands only under backups/secondary/<stack>/ (code-asserted).
Baselines (live-verified at session start)
| Repo | main @ start |
Version | → |
|---|---|---|---|
| felhom-controller | 1245a6c |
v0.134.1 | v0.135.0 |
Files
- new
internal/backup/tier2_capture.go—tier2CaptureSet(TierSecondary legs + gaps),tier2Leg,tier2DestRel. - mod
internal/backup/tier2.go—selectTier2Target(network exclusion, two sizes,StateOnly),RunTier2v2 body (migration/mirror/reconcile/marker-last),tier2SafeRemove+tier2Reconcile+classifyTier2Rel,Tier2Inforewired, header +RunTier2comments truthful; deletederrTier2MultiDir/tier2AppDataName. - mod
internal/backup/tier2_restore.go— v2 marker gate + two-subtree missing-only merge; deletederrTier2MultiDirRestore. - mod
internal/backup/offbox_restore.go— Part 0.5 scratch local-preference. - mod
internal/backup/backup.go—tier2SSDFitsseam. - mod
internal/backup/appbackup_bridge.go—ClassMandatory/Optional/Excludedaliases. - mod
internal/settings/settings.go— Part 0 seed (OffboxEnlargeNoticeSeeded+seedOffboxEnlargeNoticeat Load; getter append removed);CrossDriveBackup.LastWarning. - mod
internal/web/handlers.go+templates/backups_apps.html— surfaceTier2LastWarning. - tests new
tier2_v2_test.go,offbox_scratch_test.go; rewrotenotif_migration_test.go(Part 0),tier2_restore_test.gofixture (v2),async_restore_test.gofixture (v2),tier2_test.gosignatures; deleted obsoletetier2_appdata_test.go. - CHANGELOG / REPORT / CONTEXT / README.
Untouched: offbox engine (except offboxRestoreScratchDir), appexport, hub, agent; rsyncMirror/rsyncRestoreMissing unchanged.
Tests — results
go build ./... && go vet ./... && go test ./... — green. New: v2 suite (§7 A–H + reconcile
keep/remove + safe-remove boundary proof + classifyTier2Rel table), Part 0 (idempotent seed +
opt-out-sticks + fresh-customer), Part 0.5 (local-preference + network-only WARN).
§10 red-proofs (mutate → FAIL → revert), all verified
| ID | Mutation | Test |
|---|---|---|
| A | classified app routed to the legacy resolver | ClassifiedPaperless (export appears in dest) |
| B | legacy routed through ComputeCaptureSet | LegacyMultiDir (empty legs) |
| C | marker written before the legs | MigrationAndMarkerLast (marker present after a leg failure) |
| D-reconcile | disable stale detection | Reconcile (stale dir survives) |
| D-boundary | neuter the safe-remove prefix assert | Reconcile (removal outside destBase not refused) |
| E | SSD copies optional (no state-only filter) | SSDStateOnly (optional on the SSD) |
| F1 | drop the pinned IsNetwork check | NetworkExclusion/pinned (NAS pin honored) |
| F2 | drop the auto IsNetwork check (NFS iterated first) | NetworkExclusion/auto (NAS selected) |
| G2 | neuter the restore marker gate | RestoreRefusesOldLayout (merge runs on old layout) |
| Part 0 | don't persist the seed marker | SeedOffboxEnlargeNotice_OptOutSticks (type re-enabled) |
All reverted; post-revert full suite green; no residue.
Deploy / verify
<filled after deploy>
§13 live validation
<filled after the live legs>
6D-pending
<updated after §13>
Observations
- The
Tier2LastWarningsurfacing is minimal (one line on the app's tier-2 row); the SSD state-only hint was already rendered viaEffectiveIsSSD/EffectiveDesc(no new template needed there). tier2SSDFitsseam added becausesystem.GetDiskUsageis Linux-only (nil on the Windows test host) — production uses the real predicate.