8.2 KiB
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-root→readlink -f→ /dev/dm-1 →/sys/block/dm-1/slaves= sda3 →/sys/class/block/sda3/partitionexists → parent sda. NO /boot/efi mount (sda2 vfat exists, unmounted — classic legacy shape). Bonus fact baked into the design: a PHYSICAL disk'sslaves/dir EXISTS and is EMPTY (ls /sys/block/sda/slaves | wc -l= 0) — sohasDiralone 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 (scratchpadpve-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:SystemDisksnow grounds every system mountpoint viaphysicalDisksOf— symlink canonicalization →wholeDiskOffast path (raw disks/ partitions, unchanged) →walkSlaves: recursive/sys/block/<name>/slavesresolution 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-/devsources (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);ProcHostReaderreads<sysBlockDir>/<name>/slaves; all four test fakes mirror it (storagefakeHostReadergains aslavesmap; localapifakeHostReader/uuidHostReader/f2HostReaderreturn nil,false).- Caller audit:
handleDisks,roleForMountPath,deviceRole(localapi/disks.go) andgatherClaimFacts(storage/claim.go) — all consumeSystemDisksthroughisSystemBacked/classifyClaimfail-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_WizardLivesFAILS — "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_FailSafeFAILS — scratch classified candidate while the walk is incomplete (asserted through the REALsysKnown=false→isSystemBacked/RoleForRawDevicepath, not a lookalike).
Full suite: go test ./... green (all packages; recovery-code flake did not fire).
§13 deploy + regression (DONE) + the IA replay (agent half)
- Built on 180 (
sha256 2447d4a3…dc7a); deployed drill host first (0.86.0 → 0.87.0,.bak-0.86.0kept, service active, clean journal), then felhom-pve (0.86.0 → 0.87.0,.bak-0.86.0kept, capabilities self-check 62/62 ok, degraded=0, inactive=0). - 13.3 regression (felhom-pve, the §3 pre-captures vs post):
/disksroles BYTE-IDENTICAL (all 7 targets: felhom-usb/felhom-flash/raw-sdb user-data, local/local-lvm system, felhom-pbs/felhom-offsite backup) and/disks/candidatesidentical (empty pre and post). - The exact IA-failure replay (drill, API level — the endpoint the wizard UI invokes):
hot-added a 5 GB scsi disk to qm 300 (
local-lvm:vm-300-disk-1, appears as /dev/sdb in the drill PVE) →GET /disks/candidatesnow returnsinitialize: [{device:/dev/sdb, 5 GiB, QEMU HARDDISK, data_bearing:false}]— pre-fix the same call returned empty (drill-pre-0870.txt). The root disk sda is NOT offered (safety direction). The scratch disk is left ATTACHED for the supervised UI leg. - Published to Gitea:
AGENT_VERSION=0.87.0,AGENT_SHA256=2447d4a345115db0b380779f251750f3d8c48ba9e89aeaac162fd8c74ce8dc7a(creds from the 180 docker-config, out-of-band). felhom.eu disposition note:documentation/audits/DISPOSITION-ia-finding2-systemdisks-2026-07-13.md(commit74fa61c).
§13.2 wizard live leg — COMPLETE (operator session + GO; finished same day)
- Wizard OFFERED the hot-added 5 GB disk (Üres — formázható) on the legacy-boot drill box — the exact pre-fix dead end. Operator enrolled + formatted through the REAL UI flow (mount name enroll-test, ext4, default): "A meghajtó sikeresen inicializálva és regisztrálva: /mnt/felhom-drives/enroll-test"; storage page showed the drive Active/Default, role Felhasználói adat, uuid durable-id. (First attempt hit the controller-side CSRF bug on claimed-box wizard pages → fixed as controller v0.126.3, unrelated to the agent.)
- Teardown (leave as found): decommission attempted through the UI → correctly REFUSED by
the controller's M1 guard (last usable drive; the refusal surfaced badly → controller
v0.126.4 fixed the 502-through-Cloudflare + native-alert classes). Teardown completed at
pipeline level: agent
/disks/decommission(guest token) → ok (logical retire per design; the raw mount/unit intentionally stays for re-enroll — removed manually as the "physical removal" step), controller registry entry removed (0 storage paths — the pre-enroll state), mount unit stopped + file removed,qm disk unlink 300 --idlist scsi1 --force(LV vm-300-disk-1 destroyed). Verified: /proc/mounts clean, candidates empty, drill gate back ON. - Day-0 manifest: vouched by the operator — agent 0.87.0, sha
2447d4a3…dc7a(hub Configuration screenshot; golden 0.120.0 + MinAgent 0.81.0 unchanged).
Observations
- No
SystemDiskscaller 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 -lhits (capability/probe.go, escrow/*.go) — not touched, not mine.