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.
This commit is contained in:
@@ -1,3 +1,72 @@
|
||||
## v0.116.0 — R-116: the flag and the key finally share a row (2026-07-30)
|
||||
|
||||
**Closes the mechanism `v0.115.0` guessed at.** The absent-drive alarm was the generic
|
||||
`storage_disconnected` while its recovery was the specific `backup_target_restored` — a pair an
|
||||
operator cannot match. The cause is now measured, not reasoned: `felhom.eu`
|
||||
`audits/DIAG-r116-disks-payload-2026-07-30.md`.
|
||||
|
||||
**What the captured payload showed.** 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**:
|
||||
|
||||
| row | source | `mount_path` | `guest_path` | `backup_target` |
|
||||
|---|---|---|---|---|
|
||||
| `felhom-backup` | Observe | `""` | `""` | **`true`** |
|
||||
| `694034cc-…` | registry union | `/mnt/cel` | `/mnt/felhom-drives/cel` | field absent ⇒ **`false`** |
|
||||
|
||||
`driveTargetByPath` (controller `intermediary.go:602-618`) registers a key only from a **non-empty**
|
||||
field, so the flag-bearing row contributes **no key at all**, while the registry row owns
|
||||
`/mnt/felhom-drives/cel` — the key the gate looks up — and says `false`.
|
||||
|
||||
**Why v0.115.0 was inert, and it was not subtle.** Its fallback computed
|
||||
`StablePathForRaw(t.MountPath)` on the Observe row, and in the absent state `t.MountPath` is `""` —
|
||||
emptied by the same `exactMount` failure that empties `BackingDevice` (`observe.go:184-190`). So it was
|
||||
`StablePathForRaw("")` == `""` and it assigned nothing. **Its test passed because the fixture supplied a
|
||||
`MountPath` the real absent state does not have.** That fixture is corrected in this release, and
|
||||
red-proof 1 replays v0.115.0's exact code against the corrected shape: it fails.
|
||||
|
||||
**THE JOIN — the actual hard part.** With the device gone the two records of one drive share **no
|
||||
runtime field**: no mount, no backing device, and the Observe row's `DurableID` has already degraded off
|
||||
the fs-UUID (`path:/mnt/cel`). What they do share is **configuration** — the storage's `path` from
|
||||
`storage.cfg` on one side, the `.mount` unit's `Where` on the other. Both yield the same stable guest
|
||||
path, so that is the key the dedup now uses.
|
||||
|
||||
- `hub.StorageTarget.ConfigPath` — the configured path, carried verbatim, **`json:"-"`**. That struct is
|
||||
a cross-repo contract pinned by `testdata/host-report.golden.json` + `contract_test.go`'s key-set
|
||||
comparison; a wire-visible field would have required a matching `felhom.eu/hub` change to stay
|
||||
non-drifting, and nothing off-box needs the value.
|
||||
- `observe.go` sets it from `s.Path`. **This is NOT the fallthrough the comment at `:176-183` forbids** —
|
||||
that prohibition is about resolving a *device or UUID* from the **containing** filesystem, which would
|
||||
hand back root's identity and mis-target a DR re-attach. `s.Path` is the storage's own declaration,
|
||||
identifies nothing but itself, and is used for no resolution. `MountPath` stays empty, which is true.
|
||||
- `disks.go` tries `MountPath` first (so the present-state path and v0.115.0's behaviour are
|
||||
byte-identical) and falls back to `ConfigPath` only when the mount is genuinely gone.
|
||||
- The union loop now dedups on guest path as well as mount path, so exactly **one** row carries the drive.
|
||||
|
||||
**THE REGRESSION THIS AVOIDED, and it is why the two obvious fixes were both rejected.** The controller
|
||||
reads `d.BackupTarget && d.MountPath != ""` as *"a real drive with its own mountpoint — healthy"* and
|
||||
returns immediately (`backup_target_offer.go:79`). So **back-filling `MountPath` onto the Observe row**
|
||||
(the smallest change) and **teaching the registry row the flag** (its `MountPath` is non-empty, read from
|
||||
the by-then-stale unit file) both produce a row satisfying that predicate while the drive is missing —
|
||||
either would have silently flipped **R-114**, which shipped 2026-07-29, back to a false *healthy*.
|
||||
**R-114's correctness currently 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`, red-proofed by feeding the handler exactly what the rejected
|
||||
option would have produced.
|
||||
|
||||
**Deliberately unchanged:** `Role` (the absent target still reads `system`), the `BoundUnderParent`
|
||||
conjunction, and every wire field. Suppressing the registry row in the absent state removes its
|
||||
`uuid:` `durable_id`, its hardcoded — and false — `state: "attached"`, and its `total_bytes` copied from
|
||||
the **root** filesystem (**R-118's symptom, incidentally gone; R-118 itself is NOT fixed and stays
|
||||
open**). No consumer of those was found: wipe/decommission need the drive present, `firstOfferableDrive`
|
||||
already excludes it, and the disk-health baseline excludes UNKNOWN verdicts.
|
||||
|
||||
**Tests 845 → 849.** Four red-proofs, each mutation verified to have landed before the run:
|
||||
(1) v0.115.0's MountPath-only fallback → `isTarget[guestPath] = FALSE, rows=2`; (2) drop the guest-path
|
||||
dedup → `carried by 2 rows`; (3) give the absent row a MountPath → the R-114 guard fires; (4) over-broad
|
||||
dedup → the non-target drive loses its own row (and two pre-existing R-113 tests fail too).
|
||||
`TestPresentTargetPayloadUnchanged` pins the healthy payload field-for-field — the state the whole fleet
|
||||
is in.
|
||||
|
||||
## v0.115.0 — R-116: the backup-target flag reaches the row the controller keys on (2026-07-29)
|
||||
|
||||
**The defect, measured live in Session C.** A drive whose device vanished raised the **generic**
|
||||
|
||||
Reference in New Issue
Block a user