R-106 + R-109: the DR recipe records the resolved namespace and names the backup target (v0.118.0)

Both defects were live on both demo boxes: the recipe said namespace "root" while
storage.cfg said demo-felhom/demo-hp, and it never named which of two content=backup
dir storages holds the local archives.

R-106: the namespace came from the listed snapshot, but PBS omits `ns` per item once
the list is namespace-scoped, so it was always empty and normalised to "root". It now
resolves from the pbs STORAGE (storage.cfg's `namespace`) — the same field vzdump makes
PVE read, so the recipe cannot disagree with the backup.

R-109: backup_target resolves from the primary tier of cfg.Backup.BackupTiers(), the
function the scheduler consults, and carries the mountpoint that separates /mnt/hdd_1
from /var/lib/vz. The resolver reports the tier IN EFFECT (daemon-start config), not
agent.json on disk — a target move rewrites the file and deliberately does not restart.

Unresolvable is recorded as unresolvable: resolved|unknown plus a distinct reason,
never a default, an empty string, or a placeholder.

Needs hub v0.83.0 — AssembleDRRecipe allow-lists top-level keys, so backup_target
would otherwise be stored intact and dropped before any operator saw it.

9 tests, 4 red-proofs (each mutation asserted to have landed). Suite rc=0, 29 ok.
This commit is contained in:
2026-07-30 13:11:08 +02:00
parent 1913e12031
commit 1c8a67eece
13 changed files with 670 additions and 103 deletions
+14
View File
@@ -290,6 +290,20 @@ type StorageTarget struct {
// field here would need a matching change in the other repo to stay non-drifting. Nothing off-box
// needs this value — its only consumer is the agent's own /disks construction, in-process.
ConfigPath string `json:"-"`
// PBSNamespace is the storage's CONFIGURED PBS namespace from storage.cfg (proxmox.Storage.Namespace)
// — "" for the root namespace, set for S4 per-customer tenancy. Present ONLY on pbs targets.
//
// It exists because storage.cfg is the ONE authority on which namespace this box's backups use:
// `vzdump --storage <pbs>` makes PVE read this exact field, and the agent's own verify client is
// built from it (`cmd/felhom-agent/main.go` → `pbs.Config{Namespace: s.Namespace}`). The DR recipe
// therefore resolves the namespace from HERE and not from a listed snapshot — a namespace-scoped
// PBS list does not echo `ns` per item, so the snapshot's own field is empty and normalising that
// empty to "root" is what made the recipe claim "root" on every per-customer box (R-106).
//
// `json:"-"` for the SAME reason as ConfigPath above: this struct is a cross-repo contract pinned by
// testdata/host-report.golden.json, and nothing off-box reads this value — its only consumer is the
// agent's own dr_recipe construction, in-process.
PBSNamespace string `json:"-"`
// ClassHint is a fast|slow HINT derived from the backing disk's rotational flag — a
// hint only; the authoritative class is hub-owned (locked decision). "" when not
// derivable (network targets have no local rotational flag).