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:
@@ -35,6 +35,15 @@ func (c *Client) ListLXC(ctx context.Context) ([]Guest, error) {
|
||||
return gs, c.get(ctx, "/nodes/"+c.node+"/lxc", &gs)
|
||||
}
|
||||
|
||||
// Pool returns GET /pools/{name} (the pool's membership — the stale-lock reaper's ownership
|
||||
// registry, audit A1). Requires `Pool.Audit` at `/pool/{name}` — NOTE: `Pool.Allocate` does NOT
|
||||
// satisfy the read (spike SPIKE-a1-pool-membership-read T2: the live 403 named Pool.Audit with
|
||||
// Allocate already granted). Host-install v1.9.0+ grants it in the FelhomAgentGuest role.
|
||||
func (c *Client) Pool(ctx context.Context, name string) (PoolInfo, error) {
|
||||
var p PoolInfo
|
||||
return p, c.get(ctx, "/pools/"+url.PathEscape(name), &p)
|
||||
}
|
||||
|
||||
// GuestStatus returns GET /nodes/{node}/lxc/{vmid}/status/current. The API body
|
||||
// has no vmid field (it is in the path), so it is set from the argument.
|
||||
func (c *Client) GuestStatus(ctx context.Context, vmid int) (Guest, error) {
|
||||
|
||||
@@ -76,6 +76,21 @@ type Guest struct {
|
||||
Uptime int64 `json:"uptime"`
|
||||
}
|
||||
|
||||
// PoolInfo is GET /pools/{poolid} — the pool's identity + membership. The stale-lock recovery
|
||||
// uses it as the ownership registry: only pool members are ever scanned (audit A1).
|
||||
type PoolInfo struct {
|
||||
PoolID string `json:"poolid"`
|
||||
Members []PoolMember `json:"members"`
|
||||
}
|
||||
|
||||
// PoolMember is one entry of PoolInfo.Members. A pool can hold guests AND storages; storage
|
||||
// entries carry type "storage" and no vmid, so membership checks must filter on both (spike
|
||||
// SPIKE-a1-pool-membership-read §8).
|
||||
type PoolMember struct {
|
||||
VMID int `json:"vmid"`
|
||||
Type string `json:"type"` // "lxc" | "qemu" | "storage"
|
||||
}
|
||||
|
||||
// GuestConfig is GET /nodes/{node}/lxc/{vmid}/config. The config surface is
|
||||
// dynamic (net0..netN, mp0..mpN, unusedN), so known fields are typed and the full
|
||||
// raw map is preserved in Extra for the dynamic ones.
|
||||
|
||||
Reference in New Issue
Block a user