v0.23.0: device-ROLE classification + tiered storage-wipe gate (user-data customer-confirmable; system/backup operator-only)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 21:29:18 +02:00
parent 9e3513557f
commit 15f7529a1c
10 changed files with 759 additions and 106 deletions
+37
View File
@@ -3,6 +3,43 @@
All notable changes to **felhom-agent** are recorded here. Update on every code
change that gets pushed.
## v0.23.0 — device-ROLE classification + tiered storage-wipe gate (system/backup operator-only, user-data customer-confirmable) (2026-06-11)
The storage-authorization redesign (agent half). The gate's destructive-wipe path is now **tiered by
the device's protection ROLE**, which the agent classifies from its OWN inspection — never the
caller's claim (the storage analog of classify.go's data-bearing verdict).
- **`internal/storage/role.go`** — `DeviceRole` (`system` | `backup` | `user-data`) + the
authoritative classifier. `RoleForStorage` (storage-view targets) and `RoleForRawDevice` (a raw
device, e.g. a fresh disk in the init flow) map a device to its tier via `SystemDisks` (the
whole-disks backing `/`, `/boot`, `/boot/efi`, root-free reads). Rules: `pbs` → backup; `lvmthin` /
builtin `local` / nfs / cifs / unknown → system; `usb` / `local-dir` on a **non-system external
device** → user-data. **Fail-safe**: any ambiguity (system disks unknown, or an unrecognizable
device topology) → **system** (most-protected) — never silently user-data.
- **`GET /disks`** — each `DiskInfo` now carries `role`. The controller drives the UI from it
(system/backup get a lock + no destructive controls; user-data is customer-manageable).
- **Gate tier (`reconcile`)** — new `CustomerConfirmable` disposition + `Gate.AuthorizeStorageWipe`:
- role=**user-data** → **customer-confirmable**: allowed iff the request carries an explicit
customer confirmation **bound to the device's durable id** (the agent re-resolves the durable id
and matches; a confirmation for one disk can't wipe another). **No operator signature.** A
user-data drive is already within the in-guest controller's blast radius (it bind-mounts `/mnt`),
so customer-confirmation adds no new reach. Recorded in the **audit log** with the durable id
(`AuditRecord.DurableID`).
- role=**system**/**backup** → unchanged **operator-signature** (`pending_signature`). The
`confirmed` flag is **IGNORED** — a compromised controller asserting `confirmed:true` on a
protected device is refused **by role**. Every other destructive class (`guest_destroy`,
`decommission`, `restore_overwrite`, `key_rotation`) keeps operator-signature exactly as before.
- **`POST /disks/format`** — accepts `confirmed` + `durable_id` (inert for system/backup). The
data-bearing path tiers by role: user-data customer-confirmed → `mkfs`; user-data unconfirmed →
403 `needs_confirmation` (+ the durable id to confirm against, NOT an opsign command); system/backup
→ 403 with the operator-signature pending op (as before). Blank devices stay benign `mkfs`.
- **Tests** — `role_test.go` (demo-storage mapping + fail-safe), `storage_wipe_test.go` (the gate
refuses a `confirmed` wipe on system/backup → no exec; durable-id mismatch / missing-durable
refused; unknown role fails safe), and the localapi format-handler branches (user-data confirmed →
mkfs; user-data unconfirmed → needs_confirmation, no opsign; confirmed-but-protected → still refused).
Pairs with the controller's lockout + type-to-confirm UX + drive-list restyle.
## v0.22.0 — expose durable_id in GET /disks (enable controller-side guided storage) (2026-06-11)
One-line, read-only addition: `localapi.DiskInfo` gains `durable_id` (mapped from