v0.5.0-rc1: slice 5 Phase A — storage observe/report + watchdog (read-only, live)
Fill the slice-3 storage_targets stub and add the fast-poll storage watchdog. Read-only this phase; the host-root surface (mounts/SMART/grow/destructive gate) is Phase B. Hub-owned desired manifest is slice 10, so reconcile against it is built-but-unfed. - internal/storage: StorageTarget wire contract, durable_id derivation per type, HostReader seam (procfs/sysfs, root-free), Observer (storage_targets from ListStorage/NodeStorage + host reads, lvmthin thin-pool fill), and the watchdog (third daemon goroutine; debounced out-of-band report on a known target's attach/disconnect transition). - proxmox.Storage: additive parse-only config fields (durable_id sources). - collector StorageObserver seam; Loop.SetTrigger out-of-band report; daemon runs the watchdog as a third goroutine; StorageConfig knobs. - cross-repo golden kept byte-identical with felhom.eu/hub; bidirectional key-set test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,58 @@
|
||||
All notable changes to **felhom-agent** are recorded here. Update on every code
|
||||
change that gets pushed.
|
||||
|
||||
## v0.5.0-rc1 — slice 5 Phase A: storage observe + report + watchdog (read-only, live) (2026-06-09)
|
||||
|
||||
Phase A of the storage slice (doc 03 §7). Read-only and live: the agent now observes every
|
||||
host storage target, reports it into the host-report's `storage_targets` (previously an empty
|
||||
stub), and runs a fast-poll watchdog that pushes a disconnect to the hub in seconds. No
|
||||
host-root writes this phase (mounts/SMART/grow/destructive-gate are Phase B). The hub-owned
|
||||
desired manifest (class/role/policy/creds) is not served until slice 10, so reconcile against
|
||||
it is built-but-unfed — this phase ships only the genuinely-useful read-only footprint.
|
||||
|
||||
### Added
|
||||
- **`internal/storage` package** (new):
|
||||
- **`StorageTarget` wire contract** (`internal/hub/report.go`) — filled the slice-3 stub:
|
||||
`name`/`type`/`durable_id`/`state`/`reachable`, usage (`total`/`used`/`avail`/
|
||||
`used_fraction`), `content`, `mount_path`/`backing_device`, `class_hint` (rotational HINT
|
||||
— never authoritative; class is hub-owned), `role` (empty until slice 10), a `thin_pool`
|
||||
sub-object (lvmthin data fill; metadata fill is Phase B/`lvs`), and a `smart` sub-object
|
||||
(`UNKNOWN` until Phase B). Cross-repo golden kept byte-identical with `felhom.eu/hub` and
|
||||
guarded by the bidirectional key-set test (`contract_test.go`).
|
||||
- **`durable_id` derivation** (`durableid.go`) — deterministic per type (the DR-load-bearing
|
||||
re-attach key): fs-UUID (usb/local-dir), `server:export` (nfs/cifs), `repo+fingerprint`
|
||||
(pbs), `vg/pool` (lvmthin); never empty (falls back to a stable store id).
|
||||
- **`HostReader` seam + `ProcHostReader`** (`hostread.go`) — non-privileged `/proc/mounts`,
|
||||
`/dev/disk/by-uuid`, `/sys/.../rotational` + `removable` reads. Root-free by construction.
|
||||
- **`Observer`** (`observe.go`) — builds `[]hub.StorageTarget` from `ListStorage`/`NodeStorage`
|
||||
joined with host reads; surfaces the lvmthin thin-pool data fill prominently (warns ≥85%).
|
||||
- **Storage watchdog** (`watchdog.go`) — a third daemon goroutine fast-polling the *known*
|
||||
target set (a defined Proxmox storage and/or a previously-seen one) for
|
||||
`attached↔disconnected` transitions; on a transition it triggers an immediate, **debounced**
|
||||
out-of-band host-report. Only flags a *known* target's change (never a never-attached
|
||||
device); coalesces flaps within the debounce window (leading + trailing edge).
|
||||
`CachingKnownTargets` rate-limits the Proxmox-derived known set; `HostLiveness` probes
|
||||
device/mount presence (local) + a reachability dial (network), all non-privileged.
|
||||
- **Proxmox `Storage` type** (`internal/proxmox/types.go`) — additive parse-only config fields
|
||||
(`server`/`export`/`share`/`datastore`/`fingerprint`/`vgname`/`thinpool`) feeding durable_id.
|
||||
- **Collector `StorageObserver` seam** (`internal/hub/collect.go`) — populates `storage_targets`
|
||||
via the observer; a nil observer or an observe error degrades to empty (never sinks the
|
||||
heartbeat). Hub does not import storage (storage imports hub for the wire type).
|
||||
- **Out-of-band report trigger** (`internal/hub/loop.go`) — `Loop.SetTrigger`: a watchdog
|
||||
signal runs one extra collect→report immediately without disturbing the regular cadence.
|
||||
- **`StorageConfig`** (`internal/config`) — watchdog interval / debounce / known-refresh knobs
|
||||
(all optional; package defaults otherwise).
|
||||
- **Hub ingest** (`felhom.eu/hub`) — `hostReportPayload` now parses `storage_targets`
|
||||
(full mirror struct), persists them via `report_json`, counts + warns on disconnected
|
||||
targets, and has its own half of the bidirectional golden key-set test.
|
||||
|
||||
### Notes
|
||||
- The daemon still runs cleanly with no removable storage, no signers, and no hub manifest —
|
||||
the watchdog finds nothing to flag; storage reporting is best-effort.
|
||||
- `proxmox`/`hub`/`authz`/`reconcile` exported surfaces + their golden/adversarial tests are
|
||||
intact. No host-root writes, no destructive paths, no SMART this phase (all Phase B).
|
||||
- Version: **v0.5.0-rc1** at the Phase-A checkpoint; **v0.5.0** when Phase B lands.
|
||||
|
||||
## v0.4.0 — slice 4 Phase B: reversibility gate + signed-op consuming layer (2026-06-08)
|
||||
|
||||
The security core of slice 4: hub-supplied intent stops being trusted for destructive
|
||||
|
||||
Reference in New Issue
Block a user