cf9ce01917
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.
72 lines
3.9 KiB
Markdown
72 lines
3.9 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
|
||
|
||
`<filled after deploy>`
|
||
|
||
## §13 live validation
|
||
|
||
`<filled after the live legs>`
|
||
|
||
## 6D-pending
|
||
|
||
`<updated after §13>`
|
||
|
||
## 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.
|