Files
felhom-controller/REPORT.md
T

98 lines
6.3 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 — `.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 AF + §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.