Files
felhom-agent/REPORT.md
T

111 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/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=false` → `isSystemBacked`/`RoleForRawDevice` path, 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.0` kept, service active, clean journal), then **felhom-pve** (0.86.0 → 0.87.0,
`.bak-0.86.0` kept, capabilities self-check **62/62 ok, degraded=0, inactive=0**).
- **13.3 regression (felhom-pve, the §3 pre-captures vs post):** `/disks` roles BYTE-IDENTICAL
(all 7 targets: felhom-usb/felhom-flash/raw-sdb user-data, local/local-lvm system,
felhom-pbs/felhom-offsite backup) and `/disks/candidates` identical (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/candidates` now returns
`initialize: [{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` (commit `74fa61c`).
## §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 `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.