v0.115.0 — R-116: the backup-target flag reaches the row the controller keys on
Session C measured it live: a drive whose device vanished raised the GENERIC storage_disconnected while its return raised the SPECIFIC backup_target_restored -- an alarm and an all-clear an operator cannot pair. backup_target_absent never fired at all. The mechanism is not what the Session-C audit first said, and the difference decides the fix. RoleForStorage returns RoleSystem whenever backingDevice == "" (internal/storage/role.go:180-181). When the device goes, exactMountDevice fails, BackingDevice becomes "", the target row's role flips to system and it loses its guest path -- but keeps its MountPath. The union loop skips any drive whose MountPath is already seen, so the registry row is DEDUPED AWAY ENTIRELY. /disks carries no row with that guest path, so isTarget[guestPath] is a MISSING KEY, not a false. Setting BackupTarget on the union row -- the obvious fix -- could not have worked, because that row is not emitted when the alarm is needed. The audit is corrected in the same push. Fix: on the Observe row only, carry the guest path when the row IS the backup target and its role flipped because the device vanished. Three gates, verified not assumed: - t.BackingDevice == "" restricts it to the vanished-device flip; a genuinely system-BACKED storage has a real device and is excluded, so a dir storage at /mnt/<name> on the root disk cannot acquire a guest path. - Case B, the common fresh-box shape, is safe twice over: its target is the builtin local on /var/lib/vz and StablePathForRaw returns "" for anything not exactly /mnt/<name>, so nothing is set even before the gates apply. - It cannot make the gate read an absent drive as PRESENT. BoundUnderParent is assigned at exactly two sites, both inside guest-path blocks a system-role row never enters, so it stays false and planDriveGates computes false || false. Pinned by TestAbsentTargetRowDoesNotRegisterPresence -- getting this backwards would have silenced the alarm the fix exists to raise. The :213-214 boundary stands: no system or backup mount gains a guest path. Tests +5, asserting the emitted /disks JSON through a faithful copy of the controller's driveTargetByPath, because the failure class is "the value is on the wrong row". Red-proof: removing the block fails with "isTarget[...] is a MISSING KEY"; reverted byte-identical. Filed not closed: the two-row shape that produced this survives.
This commit is contained in:
@@ -223,6 +223,41 @@ func (s *Server) handleDisks(w http.ResponseWriter, r *http.Request, vmid int) {
|
||||
s.devicePresent(t.MountPath)
|
||||
}
|
||||
}
|
||||
// R-116: carry the GUEST PATH on the backup-target row even when its role has flipped to
|
||||
// system — but ONLY when that flip was caused by the device vanishing.
|
||||
//
|
||||
// WHY. The controller keys the drive-absent alarm on the registered StoragePath, which for an
|
||||
// external drive is the GUEST path. When the device goes, Observe's exactMountDevice fails, so
|
||||
// t.BackingDevice becomes "" and RoleForStorage returns RoleSystem (role.go:180-181) — the block
|
||||
// above is skipped and this row loses its guest path. It keeps its MountPath, so the union loop
|
||||
// below DEDUPES the registry row away (`seen[d.MountPath]`), and /disks ends up carrying NO row
|
||||
// with that guest path at all. driveTargetByPath then has no entry, isTarget[guestPath] is a
|
||||
// missing key, and the specific backup_target_absent alarm cannot fire — the generic one goes
|
||||
// out instead, while the RETURN (rows rejoined) fires the specific recovery. An unmatchable
|
||||
// pair. Measured live: felhom.eu audits/SESSION-C-2026-07-29.md §5.
|
||||
//
|
||||
// THE GATES, each load-bearing:
|
||||
// di.GuestPath == "" — never touch the user-data path above; this is a fallback, not a rule.
|
||||
// di.BackupTarget — only the target row. No other system/backup mount gains a guest path,
|
||||
// so the boundary at :213-214 stands: this is not "system mounts now
|
||||
// cross into the guest", it is "the drive the alarm is about keeps its
|
||||
// identity while it is missing".
|
||||
// t.BackingDevice == "" — ONLY the vanished-device flip. A storage that is RoleSystem because
|
||||
// it is genuinely system-BACKED has a non-empty BackingDevice and is
|
||||
// excluded. Without this gate a dir storage at /mnt/<name> living on the
|
||||
// root disk would acquire a guest path.
|
||||
//
|
||||
// Case B (the COMMON fresh-box shape) is safe twice over: the target is the builtin `local` on
|
||||
// /var/lib/vz, and StablePathForRaw returns "" for anything that is not exactly /mnt/<name>
|
||||
// (DriveNameFromRaw, intermediary.go:79-88), so nothing is set even before the gates apply.
|
||||
//
|
||||
// This cannot make the gate read an absent drive as PRESENT: BoundUnderParent is assigned only
|
||||
// inside the two guest-path blocks a system-role row never enters, so it stays false, and
|
||||
// planDriveGates computes present[gp] = present[gp] || d.BoundUnderParent. Inert by construction
|
||||
// — pinned by TestAbsentTargetRowDoesNotRegisterPresence.
|
||||
if di.GuestPath == "" && di.BackupTarget && t.BackingDevice == "" {
|
||||
di.GuestPath = StablePathForRaw(t.MountPath)
|
||||
}
|
||||
// Inspect the backing device for the UI's data-bearing hint (the authoritative check
|
||||
// is re-run at format time on the actual device).
|
||||
if t.BackingDevice != "" {
|
||||
|
||||
Reference in New Issue
Block a user