57 lines
3.0 KiB
Markdown
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.
|