v0.114.0 — R-113: drive presence means the DEVICE, not the bind
BoundUnderParent, the one field the controller's drive-absent gate keys on,
reported only "is this path a mount target in the guest's mountinfo". The
drive's raw mount at /mnt/<name> is a systemd mount unit bound to its device and
dies with it, but the agent's own bind of <raw>/felhom-data under the shared
parent is an ordinary bind: nothing ties it to the device, so its mountinfo
entry OUTLIVES the device as a stale shell. Presence read that survivor as true,
planDriveGates never produced a Stop action, and nothing fired on any channel --
not backup_target_absent, not the generic storage_disconnected. Measured live in
E-2d: detached at 10:58:37Z, silent for 4.5 minutes while the agent itself
logged "enrolled drive absent by UUID" every 20s (felhom.eu
audits/E2D-fresh-vm-2026-07-29.md §5.2).
The fix: BoundUnderParent becomes a CONJUNCTION -- bound under the parent AND
the drive's raw host mount still mounted (devicePresent, new deviceCheck seam
defaulting to isHostMountpoint). Applied at BOTH /disks construction sites. The
union path matters more, not less: it hardcodes State:"attached", so the
raw-mount check is the only device truth that row carries, and it is exactly the
shape E-2d detached.
Why a conjunction and not a replacement: half 2 alone would regress boot
ordering, where the raw drive mounts early and the bind lands ~18s later; the
gate depends on that window reading ABSENT. The conjunction leaves that
byte-identical and closes only the case the gate could never see.
Unknown is never absent: devicePresent("") returns TRUE. A false absent stops a
working customer's apps -- the failure mode of this fix, not of the bug.
Controller UNCHANGED, no MinAgent bump. BoundUnderParent has exactly one
functional consumer (planDriveGates, intermediary.go:226); every other mention
in both repos is a comment or a test, and boot convergence deliberately moved
off it to pollLiveBinds/driveBindLive. The alternative -- a new DevicePresent
bool the controller ANDs in -- was rejected as dangerous: a bool absent from an
older agent's JSON decodes to false, so every drive on a pre-0.114.0 agent would
have read ABSENT and stopped its apps.
Tests +6 in internal/localapi (208 -> 214): groups A-D plus a wire-contract test
asserting the ENCODED bound_under_parent, since that is what crosses to the
controller. Four red-proofs run and reverted (drop the conjunction on each path;
invert unknown; drop the bind half); disks.go verified byte-identical after.
NOT LIVE-VALIDATED. No drive was pulled. Leg awaiting Session C: device loss ->
gate Stop -> SetDisconnected -> backup_target_absent on the wire.
This commit is contained in:
@@ -1,3 +1,46 @@
|
||||
## v0.114.0 — R-113: drive presence means the DEVICE, not the bind (2026-07-29)
|
||||
|
||||
**The bug, measured live in E-2d.** `BoundUnderParent` — the one field the controller's drive-absent
|
||||
gate keys on — reported only *"is this path a mount target in the guest's mountinfo"*. The drive's raw
|
||||
mount at `/mnt/<name>` is a systemd mount unit bound to its device and dies with it, but the agent's own
|
||||
bind of `<raw>/felhom-data` under the shared parent is an ordinary bind: nothing ties it to the device,
|
||||
so **its mountinfo entry outlives the device as a stale shell**. Presence read that survivor as `true`,
|
||||
`planDriveGates` never produced a `Stop` action, and **nothing fired on any channel** — not
|
||||
`backup_target_absent`, not the generic `storage_disconnected`. Detached at 10:58:37Z, silent for 4½
|
||||
minutes while the agent itself logged `enrolled drive absent by UUID` every 20 s
|
||||
(`felhom.eu` `audits/E2D-fresh-vm-2026-07-29.md` §5.2).
|
||||
|
||||
**The fix.** `BoundUnderParent` becomes a **conjunction**: bound under the parent **AND** the drive's raw
|
||||
host mount still mounted (`devicePresent`, new `deviceCheck` seam defaulting to `isHostMountpoint`).
|
||||
Applied at BOTH `/disks` construction sites — the Observe path and the registry union path. The union
|
||||
path matters more, not less: it hardcodes `State:"attached"`, so the raw-mount check is the only device
|
||||
truth that row carries, and it is exactly the shape E-2d detached.
|
||||
|
||||
**Why a conjunction and not a replacement.** Half 2 alone would regress boot ordering, where the raw
|
||||
drive mounts early and the bind lands ~18 s later; the gate depends on that window reading ABSENT so apps
|
||||
stay stopped until the bind is live. The conjunction leaves that behaviour byte-identical and closes only
|
||||
the case the gate could never see. Pinned by `TestDisks_DevicePresence_BootWindowStillReadsAbsent`, whose
|
||||
red-proof is dropping the bind half.
|
||||
|
||||
**Unknown is never absent.** `devicePresent("")` returns **true**. A false absent stops a working
|
||||
customer's apps — the failure mode of this fix, not of the bug — so an unanswerable question must never
|
||||
be reported as "gone". Red-proofed by inverting it.
|
||||
|
||||
**Controller UNCHANGED, no MinAgent bump.** `BoundUnderParent` has exactly one functional consumer,
|
||||
`planDriveGates` (`intermediary.go:226`); every other mention across both repos is a comment or a test,
|
||||
and boot convergence deliberately moved off it to `pollLiveBinds`/`driveBindLive`. Tightening the field's
|
||||
meaning therefore changes exactly one decision — the broken one. The alternative (a new `DevicePresent`
|
||||
bool the controller ANDs in) was rejected as actively dangerous: a bool absent from an older agent's JSON
|
||||
decodes to `false`, so every drive on a pre-0.114.0 agent would have read ABSENT and stopped its apps.
|
||||
An older controller against this agent simply gets a more truthful bool.
|
||||
|
||||
**Tests:** +6 in `internal/localapi` (208 → 214), groups A–D plus a wire-contract test that asserts the
|
||||
encoded `bound_under_parent` field, since that is what actually crosses to the controller. Four
|
||||
red-proofs run and reverted (drop the conjunction on each path; invert unknown; drop the bind half).
|
||||
|
||||
**NOT LIVE-VALIDATED.** No drive was pulled. The leg awaiting Session C: device loss → gate `Stop` →
|
||||
`SetDisconnected` → `backup_target_absent` on the wire.
|
||||
|
||||
## v0.113.0 — E-2a: the guarded backup-target wrapper + POST /backup/target (2026-07-29)
|
||||
|
||||
**The agent cannot do this itself, and that is the point.** Creating a PVE storage needs
|
||||
|
||||
Reference in New Issue
Block a user