3603d1fc7f
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/.
76 lines
4.4 KiB
Markdown
76 lines
4.4 KiB
Markdown
# 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`), `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.go` — `tier2SSDFits` seam.
|
||
- **mod** `internal/backup/appbackup_bridge.go` — `ClassMandatory/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 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 `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.
|