v0.62.0 — A1: pool-membership ownership check for the stale-lock reaper
staleLockController.Guests() = ListLXC ∩ GET /pools/felhom members (ownership PROVEN via the pool registry, never assumed from enumeration scope); pool-read failure fail-safes the whole recovery through the existing guest-list guard. New Client.Pool read (needs Pool.Audit — host-install v1.9.0; Pool.Allocate does NOT satisfy it, spike T2). Composed pve:pool-read capability (non-critical) + --selftest pool-read line. Red-proofed negative tests drive the REAL controller over a broad-token-shaped fake. Per SPIKE-a1-pool-membership-read-2026-07-03.md; audit A1 (AUDIT-blast-radius-hostroot-localapi-2026-07-02). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,3 +1,34 @@
|
||||
## v0.62.0 — A1: pool-membership ownership check for the stale-lock reaper (2026-07-03)
|
||||
|
||||
Implements audit finding A1 (`AUDIT-blast-radius-hostroot-localapi-2026-07-02` §A) per the spike
|
||||
verdict (`SPIKE-a1-pool-membership-read-2026-07-03` — enumeration is pool-filtered under the scoped
|
||||
token, so this is defense-in-depth: a future broad-token deployment can no longer re-arm the reaper
|
||||
against co-tenant guests). Companion: host-install **v1.9.0** (`Pool.Audit` added to
|
||||
`FelhomAgentGuest`) — **rescope BEFORE deploying this agent**, else the reaper fail-safes (skips)
|
||||
until the ACL catches up.
|
||||
|
||||
- **`Client.Pool`** (proxmox/query.go): `GET /pools/{name}` → `PoolInfo{PoolID, Members[]{VMID,Type}}`.
|
||||
Requires `Pool.Audit` at `/pool/{name}`; `Pool.Allocate` does NOT satisfy the read (spike T2).
|
||||
- **`staleLockController.Guests()`** (localapi/stalelock.go): now returns `ListLXC ∩ pool members`
|
||||
(nonzero-vmid, non-storage entries only). Ownership is PROVEN via the pool registry, never assumed
|
||||
from enumeration scope. A pool-read failure returns a wrapped error ("pool membership read
|
||||
(pool=felhom): …") that rides the existing "guest list unavailable — skipping recovery" guard —
|
||||
fail-safe: NO unlock/snapshot-delete/start on ANY guest, never a fallback to the unfiltered list.
|
||||
One new INFO line per scan: `stale-lock: scanning pool guests` (pool, listed, scanned) — emitted
|
||||
by the controller (the unchanged `StaleLockController` seam can't carry the pre-intersect count).
|
||||
- **`NewStaleLockController`** gains `(pool string, logger *slog.Logger)`; main.go threads
|
||||
`reconcile.DefaultPool`.
|
||||
- **Capability surfacing**: the hub-report prober is now a composed closure — the sudo manifest
|
||||
probe + one `pve:pool-read` status (non-critical; degraded ⇒ reaper is fail-safed, visible on the
|
||||
report, no operator page). Composed in main.go; `internal/capability/` untouched.
|
||||
- **`--selftest`**: new "pool read" line (pool id + member count + guest members).
|
||||
- **Tests** (stalelock_pool_test.go, driving the REAL controller over a broad-token-shaped fake):
|
||||
`TestStaleLock_ForeignGuestNotReaped` (red-proved: intersect removed ⇒ FAILS with
|
||||
`pct unlock 5000` recorded), `TestStaleLock_PoolGuestStillReaped` (anti-over-filter),
|
||||
`TestStaleLock_PoolReadFails_SkipsAll` (red-proved: fallback-to-unfiltered ⇒ FAILS with mutations
|
||||
recorded), `TestStaleLockController_GuestsIntersect` (storage-member + empty-pool edges). The 9
|
||||
existing Server-level stalelock tests pass unmodified.
|
||||
|
||||
## docs — CLAUDE.md refresh: stable orientation, complete layout (2026-07-03)
|
||||
|
||||
No code change, no version bump. Deleted the version-pinned "Current: v0.31.0" narrative and the
|
||||
|
||||
Reference in New Issue
Block a user