v0.5.1: live-validation prep — fix unmounted-dir durable_id mis-id + watchdog UUID memory

Surfaced preparing the live USB validation on demo-felhom:
- observe.go: an unmounted removable dir-storage no longer falls through to the ROOT fs
  for its backing device/UUID — durable_id was becoming uuid:<root-uuid> (a DR mis-id that
  would re-attach the wrong disk). Now derived only from the target's own mountpoint;
  unmounted → no device + stable store:<name> durable_id. Removed containingMountDevice.
- watchdog.go: remember the fs-UUID observed while attached and backfill it onto the
  re-mount target, so re-mount works even if the known-set cache refreshed mid-drop
  (doc 03 §7 "sourced from the existing definition").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 11:01:40 +02:00
parent 9d6e49236c
commit 77b4f21450
6 changed files with 90 additions and 23 deletions
+17
View File
@@ -86,6 +86,7 @@ type Watchdog struct {
fired bool // lastFire is valid
pending bool // a transition is awaiting the debounce window
lastRemount map[string]time.Time // name -> last re-mount dispatch (rate-limit)
lastUUID map[string]string // name -> last fs-UUID observed while ATTACHED (re-mount key)
}
// WatchdogOptions configures a Watchdog. Targets, Liveness and Trigger are required; the
@@ -131,6 +132,7 @@ func NewWatchdog(opts WatchdogOptions) *Watchdog {
spawn: func(f func()) { go f() },
last: map[string]bool{},
lastRemount: map[string]time.Time{},
lastUUID: map[string]string{},
}
}
@@ -169,6 +171,21 @@ func (w *Watchdog) tick(ctx context.Context) {
return
}
// Remember each target's fs-UUID while it is observable (attached), and backfill it
// onto a target the current observe couldn't resolve (the unmounted case loses it). The
// re-mount key is "sourced from the existing definition" (doc 03 §7): the agent learns
// the UUID while attached, so a drop+return cycle can re-mount by-UUID even after the
// known-set cache refreshed mid-drop. Single-goroutine (tick), guarded for -race.
w.mu.Lock()
for i := range known {
if known[i].UUID != "" {
w.lastUUID[known[i].Name] = known[i].UUID
} else if u := w.lastUUID[known[i].Name]; u != "" {
known[i].UUID = u
}
}
w.mu.Unlock()
// Probe outside the lock.
type probe struct {
t KnownTarget