Files
felhom-controller/REPORT.md
T
2026-08-04 18:53:47 +02:00

57 lines
3.0 KiB
Markdown

# REPORT — controller v0.197.0: R-203, both halves (2026-08-04)
Deployed to demo-hp guest 9201. No hub change. Nothing deleted, wiped or moved; the R-201 drill was
not resumed.
## What shipped
**Part 1 — one resolver, five callers.** `appbackup.NamespaceRootFor` / `IsEnrolledDrive` are now the
single expression of the drive-kind rule; `backup.Manager.namespaceRoot` and `stacks.Manager.inGuest`
delegate (they were two copies that already differed over `filepath.Clean`). Routed through it:
`stacks/deploy.go` `withPathVars` (the live defect), `appexport/fabplan.go` + `export.go` via a new
`GetStackNamespaceRoot` provider method, `web/handlers.go`'s FileBrowser mounts (the fifth site, which
the spec did not name — latent today), and `stacks/delete.go`'s `ExportDataMounts` (its own commit).
`ComputeFabBuckets` now receives the namespace root, matching `ComputeCaptureSet`.
**Part 2 — the verdict.** `last_status` gains `incomplete`; the capture gaps are recorded structurally
so the verdict has something to act on; the operator hears it through the existing per-run digest.
## Proven live on demo-hp
```
bind before: /mnt/sys_drive/userdata/media/books
bind after: /mnt/sys_drive/felhom-data/userdata/media/books
capture log: 0 mandatory path(s) -> 1 mandatory path(s)
$ restic ls -l latest --tag calibre-web
-rw-r--r-- 1000 1000 181 2026-08-04 12:53:06 .../media/books/DRILL-SENTINEL.txt
```
The file, by name and size, inside the snapshot — not a green status.
## Tests
`go build && go vet && go test ./...` rc=0; `controller_gates.py --fast` OK. Table-driven over BOTH
drive kinds, because this defect survived by being invisible on the kind that already worked.
**One red-proof PASSED and the test was wrong, not the code:** the first Scenario-C test reached only
`offboxCaptureSet` while the mutation lives in `runOffboxInternal`. Replaced with a run-level test
driving `RunOffboxBackup`; it now fails under both an unreachable gap-recording and an unconditional
`ok`. Scenario D's proof also could not fail by construction (the tier filter already excludes
optional paths) — the two-part mutation that does is recorded.
## Corrections to the spec
- **Five call sites, not four.** The fifth is the FileBrowser mount builder.
- **`ExportDataMounts` is export-only despite living in `delete.go`.** Its only caller is the `.fab`
adapter, and the delete path's own `ProtectedHDDPaths` guard is layout-agnostic by construction, so
the destructive-path risk the spec flagged does not exist.
## Observations
`resolveAbs` resolves `RootHDD` and `RootUserdata` against one root — fine on enrolled drives, and now
consistent between export and backup, but whether `${HDD_PATH}` should mean the namespace root on the
system drive is a separate decision affecting every deployed app. The deploy API is
first-deployment-only (409 after), so an existing app picks up the corrected path via start/redeploy.
The CSRF form field is `_csrf`, not `csrf_token` — second session running that this cost time.