Files
felhom-agent/REPORT.md
T
admin 3c174bc6f2 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
2026-07-13 13:15:03 +02:00

6.0 KiB
Raw Blame History

REPORT — v0.87.0: SystemDisks device-mapper walk (IA finding 2, MEDIUM)

Date: 2026-07-13 · Version: felhom-agent v0.87.0 (from v0.86.0, baseline c20814e confirmed) · Agent-only classification fix; MinAgent/controller coupling: none. Safety direction: the outcome made impossible is a root-backing disk classified candidate; the fixed failure is the opposite over-protection (legacy-boot → everything system → dead wizard).

§3 spike (live, BEFORE code — both transcripts)

  • Drill host (root@192.168.0.152, the exact IA-failure shape): legacy boot — / from /dev/mapper/pve-rootreadlink -f/dev/dm-1/sys/block/dm-1/slaves = sda3/sys/class/block/sda3/partition exists → parent sda. NO /boot/efi mount (sda2 vfat exists, unmounted — classic legacy shape). Bonus fact baked into the design: a PHYSICAL disk's slaves/ dir EXISTS and is EMPTY (ls /sys/block/sda/slaves | wc -l = 0) — so hasDir alone is not resolution; the walk requires non-empty slaves to ground a virtual device.
  • felhom-pve (regression baseline): / → dm-1 → slaves sda3 → sda AND /boot/efi → /dev/sda2 → sda — ESP and walk agree on the same disk ⇒ post-fix output must be identical. Pre-upgrade API captures saved (scratchpad pve-disks-pre-0870.json, pve-cands-pre-0870.json, drill-pre-0870.txt): felhom-pve roles (felhom-usb/felhom-flash/raw-sdb user-data, local/lvm system, pbs/offsite backup), candidates EMPTY on both hosts pre-fix.

No surprises → proceeded.

Implementation (commit on main)

  • internal/storage/role.go: SystemDisks now grounds every system mountpoint via physicalDisksOf — symlink canonicalization → wholeDiskOf fast path (raw disks/ partitions, unchanged) → walkSlaves: recursive /sys/block/<name>/slaves resolution for virtual devices (dm-, md), partitions resolved by the existing regexes (sda3 → /dev/sda), cycle/depth guard (visited set, cap 32). Per-branch conservatism (operator ruling): ANY unresolvable slave — dangling entry, unrecognizable name (e.g. md0p1), virtual with empty slaves — fails the WHOLE walk → ok=false → the UNCHANGED all-system fail-safe. Non-/dev sources (ZFS dataset, NFS, overlay) are unwalkable → fail-safe (same as pre-fix).
  • internal/storage/hostread.go: HostReader.BlockSlaves(name) ([]string, bool) — the one new seam method (root-free; sysfs world-readable); ProcHostReader reads <sysBlockDir>/<name>/slaves; all four test fakes mirror it (storage fakeHostReader gains a slaves map; localapi fakeHostReader/uuidHostReader/f2HostReader return nil,false).
  • Caller audit: handleDisks, roleForMountPath, deviceRole (localapi/disks.go) and gatherClaimFacts (storage/claim.go) — all consume SystemDisks through isSystemBacked/classifyClaim fail-safes; NONE relies on all-system as a feature (finding: none needed changes). Format/mkfs paths, data-bearing guards, wizard UI untouched.
  • Behavior deltas: legacy-boot LVM → resolves, wizard lives; EFI(+LVM) → byte-identical; md-raid root → BOTH members system. New PROTECTIVE delta: an ESP-resolving host whose root topology cannot be grounded is now all-system (pre-fix trusted the ESP alone) — per the ruling's conservatism; direction-safe.

Tests + red-proofs (fake sysfs via the HostReader seam)

role_walk_test.go — fixtures mirror the §3 transcripts: legacy-LVM (dm→part→disk), md-raid (md→2 disks, BOTH asserted system), EFI+raw, EFI+LVM (felhom-pve), nested dm-on-md; dangling-slave (with a load-bearing RESOLVABLE sibling branch so a skip-mutation yields a plausible partial set); cycle; virtual-with-empty-slaves. Signature test TestSystemDisks_WalkTopologies asserts the root-backing physical disk(s) are ALWAYS in the system set + the scratch disk never is + role-level consequences — never weaken.

Red-proofs (mutate → fail → revert, verbatim):

  • A (pre-fix resolver, no walk): TestSystemDisks_LegacyBoot_WizardLives FAILS — "legacy-boot topology must resolve (the all-system fail-safe is the ERROR case…)" — the exact dead-wizard live shape; the signature table fails on legacy/md/nested too.
  • B (walk returns the dm node): SIGNATURE VIOLATION: root-backing disk /dev/sda missing from system set map[/dev/mapper/pve-root:true] (+ md0 / nested variants naming both disks).
  • D (per-branch conservatism removed): TestSystemDisks_DanglingSlave_FailSafe FAILS — scratch classified candidate while the walk is incomplete (asserted through the REAL sysKnown=falseisSystemBacked/RoleForRawDevice path, not a lookalike).

Full suite: go test ./... green (all packages; recovery-code flake did not fire).

§13 deploy + regression

  • (Filled at deploy — build 0.87.0 on 180 → drill host first with .bak-0.86.0 backup + restart + clean journal, then felhom-pve; 13.3 = post-upgrade /disks + /disks/candidates diffed against the §3 pre-captures.)

STOPPED for Viktor (per the task's STOP)

  1. 13.2 wizard live leg — hot-add the 5 GB scsi scratch disk to the drill PVE VM (qm 300 on felhom-pve), then through YOUR logged-in dashboard session (no gate-lift): wizard OFFERS the disk → enroll + format (GO required before format) → unenroll/detach, leave as found.
  2. Day-0 manifest bump to agent 0.87.0 (hub operator UI, password-gated) + the IA audit doc finding-2 disposition line (felhom.eu).

Observations

  • No SystemDisks caller relied on the all-system behavior as a feature (audited: disks.go ×3, claim.go ×1) — the fix is purely widening resolution.
  • md-raid fixture lesson: partitions of an md array (md0p1) are NOT covered by the partition regexes — such a slave fails the walk → all-system (documented in code; acceptable, exotic).
  • A physical disk's slaves/ dir exists and is empty (live-probed) — any future sysfs-walking code must treat "dir present" ≠ "virtual device".
  • Pre-existing gofmt -l hits (capability/probe.go, escrow/*.go) — not touched, not mine.