agent v0.27.0: slice 10 P3 — self-heal watchdog reconcile + 4-state intent model

IntentStore (durable-id-keyed: new/enrolled/ejected/decommissioned, OnAbsent
replug rule). Watchdog re-mounts only enrolled drives (out-of-band unmount heals;
ejected/decommissioned/new left alone) + exp-backoff flapping guard (alert@4,
cap@8). guest-attach records enrolled; eject records ejected. Non-hollow tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 17:49:25 +02:00
parent bc4f2b9168
commit 237b85f420
8 changed files with 590 additions and 35 deletions
+23
View File
@@ -3,6 +3,29 @@
All notable changes to **felhom-agent** are recorded here. Update on every code
change that gets pushed.
## v0.27.0 — slice 10 P3: self-heal watchdog reconcile + 4-state intent model (2026-06-12)
The storage watchdog goes from detect-only → detect-and-reconcile: the agent autonomously re-mounts an
enrolled external drive that dropped out-of-band (the colleague's Proxmox unmount), gated by a persisted
INTENT model so it never auto-adopts an unknown drive or fights an official eject.
- **`internal/storage/intent.go``IntentStore`** — durable, **durable-id-keyed** (UUID/WWN, never
sdX/path), atomic-write 4-state model: `new` (not recorded → never auto-mount), `enrolled` (desired
mounted → reconcile drift), `ejected` (intentional unmount → leave alone), `decommissioned`
(permanent). `OnAbsent` clears `ejected``enrolled` so a replug auto-mounts (the replug rule).
Records intent ONLY through the official enroll/eject paths — an out-of-band unmount records nothing
and is healed. Tests cover the states, persistence, the replug rule, and the reconcile gate.
- **`watchdog.go` — intent-gated reconcile + flapping guard (3C)** — the re-mount candidate (device
present, not mounted) now fires ONLY for an `enrolled` drive (via `IntentReader`); a present→absent
transition (device gone) calls `OnAbsent`. Exponential backoff (`debounce·2^fails`) + an alert after
4 failed cycles + a hard stop after 8 (no infinite loop). Failure = "still not present a full backoff
window after we dispatched" (a slow async re-mount isn't miscounted). Tests: colleague-unmount→
reconciled; ejected/new/decommissioned→left alone; ejected→absent→replug→auto-mount; flapping→caps.
- **`internal/localapi`** — `POST /disks/guest-attach` records `enrolled`; `POST /disks/eject` records
`ejected` (BEFORE unmount, while the durable-id still resolves) via the new `IntentRecorder`. `main.go`
opens one `IntentStore` (`<StateDir>/drive-intents.json`) shared by the watchdog + local API; open
failure degrades to ungated legacy remount (logged).
## v0.26.0 — slice 10 P2 activation: guest-reboot endpoint (user-triggered drive activation) (2026-06-12)
A drive enrolled into a RUNNING unprivileged guest can't be live-activated (proven: `pct set` won't