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:
@@ -1,48 +1,87 @@
|
||||
# REPORT — agent v0.86.0: DR-tier-by-default (capability `inactive` + F-3 parent ownership)
|
||||
# REPORT — v0.87.0: SystemDisks device-mapper walk (IA finding 2, MEDIUM)
|
||||
|
||||
**Date:** 2026-07-12 · **Baselines:** agent v0.85.0 (`bcb8dad`) → **v0.86.0**.
|
||||
**Source of truth:** the DR-tier-by-default batch spec + `felhom.eu/documentation/audits/DRILL-day0-vm-2026-07-12.md`.
|
||||
**Scope:** agent half of the batch — the capability `inactive` state (disabled ≠ degraded) and the
|
||||
F-3 provision parent-dir ownership fix. No escrow/ceremony changes (decision 4: identity-only
|
||||
escrow is PARKED by policy). Green gate: `go build ./... && go vet ./... && go test ./...` — all
|
||||
26 packages ok.
|
||||
**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).
|
||||
|
||||
## What shipped
|
||||
## §3 spike (live, BEFORE code — both transcripts)
|
||||
|
||||
1. **Capability `inactive` state** (`internal/capability/probe.go`, `manifest.go`):
|
||||
- `StatusInactive = "inactive"`, `ReasonInactive = "disabled by configuration"`.
|
||||
- `Capability.GatedBy` + `Prober.GateActive(gate)` — a HEALTHY probe of a gated capability
|
||||
whose gate is off is downgraded ok→inactive. Degraded (binary missing / sudo denied) is
|
||||
NEVER downgraded: an un-migrated pre-v1.15.0 box must not masquerade as deliberately off.
|
||||
- The 3 `pbsdr-*` entries are gated via the stable name prefix in `Manifest()` (a future
|
||||
pbsdr-* op is gated automatically; nothing else ever is — pinned by test).
|
||||
- `Summarize` counts only real degraded; `logCapabilities` logs an inactive count at INFO and
|
||||
now runs AFTER the pbsdr wiring so the startup snapshot equals the first report's.
|
||||
2. **`pbsdr.Manager.DRConfigured()`** (`internal/pbsdr/manager.go`): the gate's answer — live
|
||||
descriptor state (anything but `disabled`), falling back to the persisted converged marker
|
||||
before the first desired-state fetch (an applied box never flaps inactive across a restart).
|
||||
3. **F-3** (`internal/provision/backhalf.go`): a root-run provision chowns `guests/` +
|
||||
`guests/<vmid>/` to the state-dir's owner (`chown --reference`, non-recursive; bootstrap leaf
|
||||
stays guest-root). Non-root (daemon) runs skip it via the `geteuid` seam.
|
||||
- **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.
|
||||
|
||||
## Tests + red-proofs (all three mutations proven red, then restored)
|
||||
No surprises → proceeded.
|
||||
|
||||
| Test | Red-proof mutation | Result |
|
||||
|---|---|---|
|
||||
| `TestProbe_GateOffHealthyIsInactive` (+ BinaryMissingStaysDegraded, GateOnOrNilIsOK, ExactlyPBSDRGated) | gate branch in `Probe` disabled (`if false`) | inactive case FAILED; degraded case stayed green ✓ |
|
||||
| `TestDRConfigured_Lifecycle` (unconfigured→applied→restart-marker→disabled) | `DRConfigured` ignores the `disabled` state | disabled case FAILED ✓ |
|
||||
| `TestProvision_RootRunOwnsGuestsParents` (root issues, non-root doesn't, never `-R`) | F-3 chown block removed | root case FAILED ✓ |
|
||||
## Implementation (commit on main)
|
||||
|
||||
## Live state touched
|
||||
- `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.
|
||||
|
||||
- **felhom-pve latent F-3 state FIXED live** (pre-existing `guests{,/9201}` root:root 0700 →
|
||||
chowned to felhom-agent, bootstrap subtree untouched — the drill's proven fix).
|
||||
- **Peti's host: unreachable** — his F-3 check + agent update stay deferred (publish-train D/E/G).
|
||||
## Tests + red-proofs (fake sysfs via the HostReader seam)
|
||||
|
||||
## Shipping notes
|
||||
`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.
|
||||
|
||||
- `configs/felhom-pbs-apply` was already in this repo; host-install **v1.15.0** (felhom.eu,
|
||||
same-day) now ships it (drill F-7) + `age` (F-10) + `wg_tunnel.enabled: true` (F-9/decision 5).
|
||||
- Hub-side rendering of the `inactive` chip + the per-customer DR flag = hub v0.51.0 (felhom.eu).
|
||||
- Deploy: build+push 0.86.0 on 180, deploy to felhom-pve, publish via `scripts/publish-agent.sh`,
|
||||
hub Day-0 manifest vouch as operator/browser follow-up.
|
||||
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
|
||||
|
||||
- (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.
|
||||
|
||||
Reference in New Issue
Block a user