agent v0.87.0: SystemDisks device-mapper walk — legacy-boot hosts get a working drive wizard (IA finding 2, MEDIUM)

Operator ruling 2026-07-13: walk the root's backing device through /sys/block/<dev>/slaves
recursively down to physical disks (dm AND md; topology, never VG names); those + any mounted-ESP
holder are system; the all-system fail-safe returns to being the WALK-FAILURE error case only.
SAFETY DIRECTION: a root-backing disk classified candidate is made impossible — per-branch
conservatism (any unresolvable slave fails the WHOLE walk -> ok=false -> the unchanged all-system
path).

- physicalDisksOf/walkSlaves in role.go (symlink canon -> wholeDiskOf fast path -> recursive
  slaves walk; cycle/depth guard; non-/dev sources unwalkable)
- HostReader.BlockSlaves(name) — the ONE new seam method; ProcHostReader reads
  /sys/block/<name>/slaves; all four test fakes mirror it
- role_walk_test.go: signature table (root-backing disk ALWAYS system across legacy-LVM /
  md-raid / EFI+raw / EFI+LVM / nested dm-on-md — NEVER weaken) + dead-wizard-lives +
  dangling-slave fail-safe (real sysKnown=false path) + cycle + empty-slaves; red-proofs A/B/D
  run->fail->revert (recorded in REPORT)
- §3 spike transcripts (drill legacy: dm-1->sda3->sda; felhom-pve: ESP+walk agree on sda ->
  byte-identical regression); caller audit: none relied on all-system as a feature
- format/mkfs paths, data-bearing guards, wizard UI untouched
This commit is contained in:
2026-07-13 13:15:03 +02:00
parent c20814e6c2
commit 3c174bc6f2
11 changed files with 450 additions and 63 deletions
+9 -4
View File
@@ -36,10 +36,11 @@ func (f *fakeStorageAPI) NodeStorage(context.Context) ([]proxmox.Storage, error)
type fakeHostReader struct {
mounts []Mount
mountsErr error
uuids map[string]string // device -> uuid
exists map[string]bool // device -> present
rotational map[string]bool // device -> rotational (presence => known)
removable map[string]bool // device -> removable (presence => known)
uuids map[string]string // device -> uuid
exists map[string]bool // device -> present
rotational map[string]bool // device -> rotational (presence => known)
removable map[string]bool // device -> removable (presence => known)
slaves map[string][]string // block NAME -> sysfs slaves (presence => /sys/block dir exists)
}
func (h *fakeHostReader) Mounts() ([]Mount, error) { return h.mounts, h.mountsErr }
@@ -56,6 +57,10 @@ func (h *fakeHostReader) Removable(device string) (bool, bool) {
v, ok := h.removable[device]
return v, ok
}
func (h *fakeHostReader) BlockSlaves(name string) ([]string, bool) {
s, ok := h.slaves[name]
return s, ok
}
// byName indexes observed targets for assertions.
func byName(targets []hub.StorageTarget) map[string]hub.StorageTarget {