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

3.0 KiB

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.