98 lines
6.3 KiB
Markdown
98 lines
6.3 KiB
Markdown
# REPORT — `.fab` exclusion scoping (Task 4) — controller v0.136.0
|
||
|
||
## Summary
|
||
|
||
Task 4: the `.fab` column of the matrix (SQ5 verdict + ruling #1 + Viktor's R1-C). **The SQ6
|
||
over-capture is fixed for classified apps** — a manual export's userdata root tar is now
|
||
exclude-scoped (keeps only ancestors/descendants of a selected bind), so sibling apps' content no
|
||
longer rides along; an all-excluded app produces no userdata tar at all. Mechanics unchanged from
|
||
v0.130.0 (one root tar + per-mount skip), so the **manifest stays v1 and the import side is
|
||
untouched**; **legacy apps export byte-identically**. Mandatory is a server-side floor; optional is a
|
||
pre-selected checkbox; excluded is opt-in behind the two-number warning.
|
||
|
||
## Baselines (live-verified at session start)
|
||
|
||
| Repo | `main` @ start | Version | → |
|
||
|---|---|---|---|
|
||
| felhom-controller | `5f21613` | v0.135.0 | **v0.136.0** |
|
||
|
||
## Files
|
||
|
||
- **mod** `internal/appbackup/captureset.go` — extracted `resolveGuardCollapse` (shared pipeline); new `ComputeFabBuckets` + `FabBuckets`.
|
||
- **new** `internal/appexport/fabplan.go` — `fabPlan`, `computeFabPlan`, `fabUserdataExcludes` + `classifyFabRel`, `tarDirectoryExcluding`, `fabEstimateSplit`.
|
||
- **mod** `internal/appexport/export.go` — `ExportRequest` += `DeselectOptional`/`OptInExcluded`; `dirLister` seam; `exportHDDData` consumes the plan; `tarDirectory` → thin wrapper.
|
||
- **mod** `internal/appexport/estimate.go` — `ExportEstimate` class-split fields (additive); `FabItem`.
|
||
- **mod** `internal/appexport/provider.go` + `cmd/controller/main.go` — `GetStackClassifiedBinds` on the export seam + adapter.
|
||
- **mod** `internal/web/handler_export.go` + `handler_export_download.go` — both start handlers carry the selections (two-call-site).
|
||
- **mod** `internal/web/templates/app_export.html` — the class-selection UI.
|
||
- **tests** new `fabbuckets_test.go`, `fabplan_test.go`, `fabexport_test.go`, `fab_export_test.go`; provider fakes updated.
|
||
- CHANGELOG / REPORT / CONTEXT / README.
|
||
|
||
Untouched (§5/§12): `manifest.go`, `validate.go`, `restore.go`, every import handler, `ExportDataMounts` — the verdict rests on the import side staying byte-identical.
|
||
|
||
## Tests — results
|
||
|
||
`go build ./... && go vet ./... && go test ./...` — **green.** New: bucket/guard/no-containment
|
||
(`ComputeFabBuckets`); plan scenarios A–F + §8 (unmatched-mount-kept, skip-when-none, mandatory floor,
|
||
nested excludes); `tarDirectoryExcluding` FS-level; export-level bundle tests (exclude-scoped tar,
|
||
legacy full-root, all-excluded no-tar); the two-call-site bundle test across BOTH start pipelines.
|
||
|
||
### §10 red-proofs (mutate → FAIL → revert), all verified
|
||
|
||
| ID | Mutation | Test |
|
||
|---|---|---|
|
||
| A | legacy routed through the classified plan (drop short-circuit) | `FabExport_LegacyFullRoot` (userdata tar skipped) |
|
||
| B | always-tar the userdata root | `FabExport_AllExcludedNoUserdataTar` (tar present) |
|
||
| C | invert the keep-rule | `FabExport_ExcludeScopedUserdataTar` (mandatory excluded) |
|
||
| D3 | drop the server-side mandatory floor | `FabPlan_MandatoryFloor` (mandatory deselected) |
|
||
| E | skip guards for excluded buckets | `ComputeFabBuckets_GuardsAllClasses` (traversal enters a bucket) |
|
||
| F | drop selection fields from one start handler | `Fab_SelectionsRideBothStartPipelines/download` (opted-in content absent) |
|
||
|
||
All reverted; post-revert full suite green; no residue.
|
||
|
||
## Deploy / verify
|
||
|
||
Built + pushed `felhom-controller:0.136.0` on 180; deployed to guest 9201 → `Up (healthy)`. Commit
|
||
`cf9ce01`.
|
||
|
||
## §13 live validation — the SQ6 before/after, on the real demo
|
||
|
||
- **Leg 1 — before-picture (v0.135.0, the SQ6 protocol):** planted `SPIKE-marker-own.txt` under
|
||
calibre-web's `media/books/` and `SPIKE-marker-sibling.txt` under the sibling `media/movies/`;
|
||
exported calibre-web via the real UI (download pipeline, 356 MB). `tar tf` of the bundle's
|
||
`data/hdd/userdata.tar`: **BOTH markers present** (own=1, sibling=1, 15 `media/movies` entries) —
|
||
the whole userdata root rode along (the over-capture).
|
||
- **Leg 2 — after (v0.136.0):** re-exported calibre-web (default). `tar tf`: **own=1, sibling=0,
|
||
`media/movies` entries=0, `media/books` entries=39** — the sibling no longer rides along (Scenario C
|
||
live). radarr default export → the bundle has **no `hdd/` contents at all** (no userdata tar,
|
||
Scenario B live — radarr is classified all-excluded).
|
||
- **Leg 3 — opt-in + estimate split:** radarr's `download/estimate` returns
|
||
`has_classification:true` + `excluded_items:[{downloads, 308 MB}, {media/movies, 2.7 GB}]` (the
|
||
two-number-warning data, live, both pipelines). Re-exported radarr with
|
||
`opt_in_excluded:["userdata/downloads"]` → the userdata tar now carries `downloads` (2 entries) while
|
||
`media/movies` stays excluded (0) — the opt-in flips Scenario B, per-path, live.
|
||
- **Leg 4 — import compatibility (read-only):** the new exclude-scoped bundle's manifest is **v1**
|
||
(`version=1`, `hdd_subdirs=['userdata']`, `has_hdd_data=true`) — the untouched import maps the
|
||
`userdata` basename → `<HDD_PATH>/userdata` via the existing fallback, so the smaller tar restores
|
||
exactly as before. A destructive live import over calibre-web (`compose down --volumes`) was NOT run
|
||
— compatibility is structurally guaranteed (v1 manifest + untouched import) and unit-proven
|
||
(`roundtrip_test.go`).
|
||
- **Leg 5 — hygiene:** removed the two SPIKE markers from live userdata and the test `.fab` bundles;
|
||
cleaned the 180 session temp. Listed removals above.
|
||
|
||
## 6D-pending
|
||
|
||
- **CAMPAIGN-6D Accept #1** (the ≥1 GiB `.fab` full circle) now runs against this final capture shape —
|
||
a real export→transport→import round-trip of a large classified app, exercising the exclude-scoped
|
||
userdata tar end-to-end (the live leg-4 import was deliberately read-only here).
|
||
- Carried from earlier tasks: `PlaceOffsiteRestore` against live data; the SQ3 immich offsite-only
|
||
full circle; organic enlarge-block firing.
|
||
|
||
## Observations
|
||
|
||
- `ComputeCaptureSet` was refactored to share `resolveGuardCollapse` with `ComputeFabBuckets` (no
|
||
duplicate pipeline); its existing tests stayed green through the refactor.
|
||
- The estimate split is in the shared `EstimateExport`, so both estimate pipelines surface it by
|
||
construction; the genuine two-call-site risk (and the F red-proof) is on the START selections, which
|
||
the bundle test exercises per-handler.
|