docs: REPORT — v0.62.0 A1 fix shipped + live-verified (rescope → deploy → 47/47 caps, pool scan pool=felhom listed=1 scanned=1)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,19 +1,98 @@
|
|||||||
# REPORT — docs: CLAUDE.md refresh (stable orientation, complete layout)
|
# REPORT — v0.62.0: A1 pool-membership ownership check for the stale-lock reaper
|
||||||
|
|
||||||
**Date:** 2026-07-03 · **Class:** docs-only (no code, no version bump, no deploy)
|
**Date:** 2026-07-03 · **Class:** implementation (agent v0.61.0 → **v0.62.0**, live on felhom-pve)
|
||||||
**Baseline:** `main` @ `2a024b6039` (v0.61.0 live on felhom-pve — unchanged)
|
**Spec basis:** audit A1 (`AUDIT-blast-radius-hostroot-localapi-2026-07-02` §A) via the spike verdict
|
||||||
|
(`felhom.eu/documentation/audits/SPIKE-a1-pool-membership-read-2026-07-03.md` Q3/Q4/Q5).
|
||||||
|
**Companion:** felhom.eu `felhom-host-install.sh` v1.8.0 → **v1.9.0** (`Pool.Audit` in FelhomAgentGuest).
|
||||||
|
|
||||||
CLAUDE.md rewritten (diffstat: +86/−71):
|
## 1. Baselines & commits
|
||||||
- Deleted the "Current: v0.31.0" narrative + per-slice history (stale by 30 versions; current state
|
|
||||||
lives in CONTEXT.md + CHANGELOG top — standing rule: no version-pinned state in CLAUDE.md).
|
|
||||||
- Layout completed: all 17 `internal/` packages + `cmd/felhom-opsign` (old file listed 9; verified
|
|
||||||
against the tree). Missing were: capability, desired, escrow, guesthook, lanresolver, localapi,
|
|
||||||
provision, signedjobs.
|
|
||||||
- Build/deploy compressed to a summary table + `felhom-build-deploy` skill pointer. Facts re-verified
|
|
||||||
live on felhom-pve: non-root `felhom-agent` service user, `ExecStart=/usr/local/bin/felhom-agent
|
|
||||||
--config /etc/felhom-agent/agent.json`, sudoers at `/etc/sudoers.d/felhom-agent`, live `--version`
|
|
||||||
= 0.61.0.
|
|
||||||
- Kept verbatim: the Proxmox-model load-bearing rules (API-first, 3-exception root-CLI fence, UPID
|
|
||||||
WaitTask, TLS pinning, privsep gotcha), demo-host section, trunk-based + RUNBOOK conventions.
|
|
||||||
|
|
||||||
Consolidated cross-repo report: `felhom.eu/REPORT.md`.
|
| Repo | Baseline | Shipped commit |
|
||||||
|
|---|---|---|
|
||||||
|
| felhom-agent | `3e760a559a` (v0.61.0) | **`3f37c5f`** — v0.62.0 code + tests + REUSE/CHANGELOG/CONTEXT |
|
||||||
|
| felhom.eu | `5e3dfcb714` (script v1.8.0) | **`639a57e`** — script v1.9.0 + scripts/CHANGELOG + REUSE §2 errata + CONTEXT |
|
||||||
|
|
||||||
|
## 2. What changed (agent)
|
||||||
|
|
||||||
|
- `Client.Pool(ctx, name)` (proxmox/query.go): `GET /pools/{name}` → `PoolInfo{PoolID, Members[]{VMID,Type}}`.
|
||||||
|
API-side read — the `Client`-never-shells / `Privileged`-never-HTTPs fence is untouched.
|
||||||
|
- `staleLockController.Guests()` (localapi/stalelock.go): **`ListLXC ∩ pool members`** (nonzero-vmid,
|
||||||
|
non-`storage` entries). Pool-read failure → wrapped error `pool membership read (pool=felhom): …`
|
||||||
|
→ rides the pre-existing "guest list unavailable — skipping recovery" guard (`stalelock.go`
|
||||||
|
Server path). NEVER falls back to the unfiltered list. One INFO line per scan:
|
||||||
|
`stale-lock: scanning pool guests` (pool, listed, scanned). **Note (spec deviation, by
|
||||||
|
necessity):** the spec located this line in `RecoverStaleLockedGuests`; it is emitted by the
|
||||||
|
production controller instead, because the unchanged `StaleLockController` seam cannot carry the
|
||||||
|
pool name + pre-intersect count to the Server without breaking the 9 existing fake-based tests.
|
||||||
|
Net startup-log effect is identical (Scenario D line observed live, §5).
|
||||||
|
- `NewStaleLockController(px, runner, pool, logger)`; main.go threads `reconcile.DefaultPool`.
|
||||||
|
- Capability: composed closure in main.go appends `pve:pool-read` (non-critical) to the sudo
|
||||||
|
prober's snapshot; `internal/capability/` untouched. `--selftest` gained a "pool read" line.
|
||||||
|
|
||||||
|
## 3. Test results (all green: `go build ./... && go vet ./... && go test ./...`, 17 pkgs ok)
|
||||||
|
|
||||||
|
| Test | Result |
|
||||||
|
|---|---|
|
||||||
|
| `TestStaleLock_ForeignGuestNotReaped` (Scenario A) | PASS — foreign 5000 never unlocked/snap-deleted/started |
|
||||||
|
| `TestStaleLock_PoolGuestStillReaped` (Scenario B) | PASS — owned 9201 fully recovered (unlock+delsnap+start) |
|
||||||
|
| `TestStaleLock_PoolReadFails_SkipsAll` (Scenario C) | PASS — zero mutations on pool-read error; error text names the pool read |
|
||||||
|
| `TestStaleLockController_GuestsIntersect` (§8 edges) | PASS — storage-type/vmid-0 members excluded; empty pool = empty scan, no error |
|
||||||
|
| 9 pre-existing stalelock tests | PASS **unmodified** (Server-level `fakeStaleLock` seam untouched by design) |
|
||||||
|
|
||||||
|
**Red-proofs (both run and reverted):**
|
||||||
|
- **A:** intersect replaced with raw-ListLXC passthrough → `TestStaleLock_ForeignGuestNotReaped`
|
||||||
|
**FAILED** with `runner calls=[[pct unlock 5000]]` (the exact A1 exploit) and
|
||||||
|
`TestStaleLockController_GuestsIntersect` failed with both vmids returned. Restored.
|
||||||
|
- **C:** pool-read error changed to fall back to the unfiltered list →
|
||||||
|
`TestStaleLock_PoolReadFails_SkipsAll` **FAILED** with `runner=[[pct unlock 9201]]
|
||||||
|
delsnap=[9201] started=[9201]`. Restored.
|
||||||
|
|
||||||
|
## 4. Live rescope (felhom-pve, PUSHED v1.9.0 script fetched from Gitea raw, `--rescope-acl --vmid 9201`)
|
||||||
|
|
||||||
|
Roles after (only Guest changed; Base/Store byte-identical):
|
||||||
|
|
||||||
|
```
|
||||||
|
FelhomAgentStore → Datastore.Allocate,Datastore.AllocateSpace
|
||||||
|
FelhomAgentGuest → Pool.Allocate,Pool.Audit,VM.Allocate,VM.Audit,VM.Backup,VM.Config.CPU,
|
||||||
|
VM.Config.Disk,VM.Config.Memory,VM.Config.Network,VM.Config.Options,
|
||||||
|
VM.PowerMgmt,VM.Snapshot,VM.Snapshot.Rollback
|
||||||
|
FelhomAgentBase → Datastore.Audit,SDN.Use,Sys.Audit
|
||||||
|
```
|
||||||
|
|
||||||
|
Production-token `GET /pools/felhom` (403 in the spike) → **HTTP 200, members [9201]**. The running
|
||||||
|
v0.61.0 agent stayed `active` through the rescope (order: rescope BEFORE agent deploy, per spec).
|
||||||
|
|
||||||
|
## 5. Agent deploy verification (felhom-pve)
|
||||||
|
|
||||||
|
- Binary built on 180 from `3f37c5f`, deployed with backup `felhom-agent.bak-0.61.0`;
|
||||||
|
`felhom-agent --version` → **`felhom-agent 0.62.0`**; service `active`.
|
||||||
|
- Startup log (live excerpt): `msg="capabilities self-check" ok=47 total=47 degraded=0` (46 → 47:
|
||||||
|
the new `pve:pool-read`, OK) and
|
||||||
|
`msg="stale-lock: scanning pool guests" pool=felhom listed=1 scanned=1` (listed=1 because the
|
||||||
|
scoped token's ListLXC is already pool-filtered — spike T1, expected). ReassertGuestBinds clean
|
||||||
|
(logs only on action/failure; none emitted). No new warnings — the pre-existing PBS
|
||||||
|
token-secret WARN (BUNDLE leftover, CONTEXT open thread) is unchanged.
|
||||||
|
- `--selftest`: `[ ok ] pool read pool "felhom", 1 member(s)` / `- 9201 type=lxc`; suite `OK`.
|
||||||
|
- Hub-side: latest stored host report (agent_version 0.62.0) carries
|
||||||
|
`{"name":"pve:pool-read","feature":"stale-lock recovery scoping (pool ownership check)",
|
||||||
|
"critical":false,"status":"ok"}` — 47 capabilities, 0 degraded. Verified against a snapshot of
|
||||||
|
the hub DB (`host_reports.report_json`), since the operator UI is password-gated.
|
||||||
|
- Published: Gitea generic package `felhom-agent/0.62.0`, round-trip sha256 verified:
|
||||||
|
`ed1192c9ae8f355dee19128cf4b01984ba7398e860b4a16a8009655b84b32d81`.
|
||||||
|
|
||||||
|
## 6. Operator follow-ups
|
||||||
|
|
||||||
|
- **Hub Day-0 artifact manifest still vouches 0.60.0 → bump to 0.62.0**
|
||||||
|
(sha256 above; password-gated UI — CC cannot).
|
||||||
|
- Rotation + de-git of the 3 committed secrets in `felhom.eu/manifests/felhom.secret.yaml`
|
||||||
|
(spike appendix; REUSE §2 errata now records that `gitea-creds` is NOT out-of-band).
|
||||||
|
|
||||||
|
## 7. Observations (not acted on)
|
||||||
|
|
||||||
|
- Hub capability-name handling confirmed generic: the checker keys on `Critical && status=="degraded"`
|
||||||
|
only (`hub/internal/monitor/host_capability.go`), names are data — the new non-critical entry
|
||||||
|
rides the report/UI without paging and needed no hub change.
|
||||||
|
- The spike Q6 consumer observations (hub collect / disks / reconcile `ListLXC` users) remain
|
||||||
|
observations; no other consumer touched, per spec.
|
||||||
|
- `_state_get provisioned_vmid` was empty on felhom-pve (state.json holds only `completed`), so
|
||||||
|
`--rescope-acl` needed the explicit `--vmid 9201` — worth knowing for other boxes.
|
||||||
|
|||||||
Reference in New Issue
Block a user