2c4efed5de3238fadbbafd2aaf0e35117d902b54
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
21b0164fad |
R-116 (v0.116.0): give the backup-target flag and the gate's key the same row
The absent-drive alarm was generic while its recovery was specific -- a pair an operator cannot match. Mechanism now measured, not reasoned (felhom.eu audits/DIAG-r116-disks-payload-2026-07-30.md): with the device gone /disks returns 4 rows, not 3. The drive appears TWICE and the two facts the controller needs are on different rows -- the Observe row has backup_target:true but mount_path:"" and guest_path:"" (so driveTargetByPath registers NO key from it), while the registry row owns /mnt/felhom-drives/<name>, the key the gate looks up, with BackupTarget absent from its struct literal => false. WHY v0.115.0 WAS INERT: its fallback computed StablePathForRaw(t.MountPath), and in the absent state MountPath is ALSO "" -- emptied by the same exactMount failure that empties BackingDevice. It assigned nothing. Its test passed because the fixture supplied a MountPath production never supplies, and the harness left DriveTargets nil so the union loop never ran. Both corrected here; red-proof 1 replays v0.115.0's exact code against the real shape and it fails. THE JOIN, which was the hard part: with the device gone the two records share no runtime field -- no mount, no backing device, and the Observe row's DurableID has degraded off the fs-UUID. They share CONFIGURATION: storage.cfg's path on one side, the .mount unit's Where on the other, both yielding the same stable guest path. New hub.StorageTarget.ConfigPath (json:"-" -- that struct is a cross-repo contract pinned by the golden + contract_test key-set comparison, and nothing off-box needs the value), set from s.Path in observe.go, consulted in disks.go only after MountPath so the present-state path is byte-identical, plus a guest-path arm on the union dedup so exactly one row carries the drive. WHY NEITHER OBVIOUS OPTION WAS TAKEN -- both regress R-114, which shipped yesterday. backup_target_offer.go:79 reads (BackupTarget && MountPath != "") as "a real drive with its own mountpoint -- healthy" and returns before its TargetAbsent branch. Back-filling MountPath onto the Observe row (the smallest change, and the spec's lean) and teaching the registry row the flag (its MountPath is non-empty, read from the stale unit file) BOTH manufacture that row while the drive is missing, which would have told the customer the backup target is fine while its drive is gone. R-114's correctness rests on the absent-state rows not combining the flag with a mount path; that coupling was invisible until the payload existed. Pinned by TestAbsentTargetKeepsR114DegradedSignal. Role unchanged, BoundUnderParent conjunction not widened, no wire field changed. Suppressing the registry row in the absent state also removes its false state:"attached" and its root-filesystem-derived total_bytes -- R-118's symptom goes incidentally; R-118 is NOT fixed and stays open. Tests 845 -> 849, suite rc=0 read separately from this commit. Four red-proofs, each mutation asserted to have landed first. NOT live-validated at this commit: publish+vouch, C5, discrimination, over-correction. |
||
|
|
a58239f6de |
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. |