SQ6 over-capture FIXED for classified apps: the userdata root tar is exclude-scoped (keeps only ancestors/descendants of a SELECTED bind relpath — R1-C, the tier2Reconcile keep-rule); no selected userdata bind → no root tar (radarr state-only). New appbackup.ComputeFabBuckets (shared resolveGuardCollapse pipeline; class buckets; guards over ALL classes; no cross-bucket containment). appexport/fabplan.go: computeFabPlan + tarDirectoryExcluding + fabEstimateSplit. ExportRequest gains DeselectOptional/OptInExcluded (both start handlers — two-call-site); mandatory is a server-side floor. Manifest v1 + import UNTOUCHED; legacy apps byte-identical to v0.130.0. Estimate additive class split; export UI: locked-mandatory/optional-checkboxes/excluded-opt-in + two-number warning. All 6 §10 red-proofs verified. 6D Accept #1 now runs against this shape.
3.9 KiB
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— extractedresolveGuardCollapse(shared pipeline); newComputeFabBuckets+FabBuckets. - new
internal/appexport/fabplan.go—fabPlan,computeFabPlan,fabUserdataExcludes+classifyFabRel,tarDirectoryExcluding,fabEstimateSplit. - mod
internal/appexport/export.go—ExportRequest+=DeselectOptional/OptInExcluded;dirListerseam;exportHDDDataconsumes the plan;tarDirectory→ thin wrapper. - mod
internal/appexport/estimate.go—ExportEstimateclass-split fields (additive);FabItem. - mod
internal/appexport/provider.go+cmd/controller/main.go—GetStackClassifiedBindson 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
<filled after deploy>
§13 live validation
<filled after the live legs>
6D-pending
<updated after §13>
Observations
ComputeCaptureSetwas refactored to shareresolveGuardCollapsewithComputeFabBuckets(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.