# 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 `` ## §13 live validation `` ## 6D-pending `` ## 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.