Files
felhom-controller/REPORT.md
T
admin 3603d1fc7f Tier-2 engine rework: class-driven legs, v2 layout, NAS-target exclusion (Task 3b, v0.135.0)
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/.
2026-07-15 10:10:20 +02:00

4.4 KiB
Raw Blame History

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.gotier2CaptureSet (TierSecondary legs + gaps), tier2Leg, tier2DestRel.
  • mod internal/backup/tier2.goselectTier2Target (network exclusion, two sizes, StateOnly), RunTier2 v2 body (migration/mirror/reconcile/marker-last), tier2SafeRemove + tier2Reconcile + classifyTier2Rel, Tier2Info rewired, header + RunTier2 comments truthful; deleted errTier2MultiDir/tier2AppDataName.
  • mod internal/backup/tier2_restore.go — v2 marker gate + two-subtree missing-only merge; deleted errTier2MultiDirRestore.
  • mod internal/backup/offbox_restore.go — Part 0.5 scratch local-preference.
  • mod internal/backup/backup.gotier2SSDFits seam.
  • mod internal/backup/appbackup_bridge.goClassMandatory/Optional/Excluded aliases.
  • mod internal/settings/settings.go — Part 0 seed (OffboxEnlargeNoticeSeeded + seedOffboxEnlargeNotice at Load; getter append removed); CrossDriveBackup.LastWarning.
  • mod internal/web/handlers.go + templates/backups_apps.html — surface Tier2LastWarning.
  • tests new tier2_v2_test.go, offbox_scratch_test.go; rewrote notif_migration_test.go (Part 0), tier2_restore_test.go fixture (v2), async_restore_test.go fixture (v2), tier2_test.go signatures; deleted obsolete tier2_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 AH + 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 Tier2LastWarning surfacing is minimal (one line on the app's tier-2 row); the SSD state-only hint was already rendered via EffectiveIsSSD/EffectiveDesc (no new template needed there).
  • tier2SSDFits seam added because system.GetDiskUsage is Linux-only (nil on the Windows test host) — production uses the real predicate.