# REPORT — restore guests INTO the felhom pool (pool-scoped-ACL enabler, agent v0.53.0) **Date:** 2026-07-01 · **Repo:** `felhom-agent` · **Class:** Risky/supervised (agent code normal; the live token re-scope was the supervised step — executed + validated on felhom-pve). Colleague-safety batch #4 phase b (agent half). Makes every restore allocate the guest INTO the `felhom` pool so the agent token can be scoped to `/pool/felhom` + `/storage/` (blast-radius containment on a shared host). Grounded by `felhom.eu/documentation/audits/SPIKE-pool-scoped-acl-2026-07-01.md` (PASS). ## 1. Baseline → target felhom-agent `0573ec9` (v0.52.0) → **v0.53.0**. No engine/ACL logic beyond the restore param. ## 2. Files changed + commit - `internal/proxmox/mutate.go` — `RestoreLXCOptions.Pool` + `RestoreLXC` sends `pool=` when non-empty. - `internal/reconcile/bringup.go` — `const DefaultPool = "felhom"`; `BringUpSpec.Pool`; bring-up restore passes `Pool: spec.Pool`. - `internal/reconcile/restoretest.go` — scratch restore passes `Pool: DefaultPool` (residual #2). - `cmd/felhom-agent/main.go` — both `BringUpSpec` literals (bring-up/DR + provision) set `DefaultPool`. - Tests: `internal/proxmox/restore_test.go` (`TestRestoreLXC_PoolParam`), `internal/reconcile/bringup_test.go` (`TestRestoreSitesUsePool`). `CHANGELOG.md`, `README.md`. - Commit **`7639ab5`** on `main` (parent `33e3443`). ## 3. Green gate `go build ./...`, `go vet ./...`, `go test ./...` — clean (local + build server 180). ## 4. Tests + red-proofs (§10) - `TestRestoreLXC_PoolParam` PASS: `Pool:"felhom"` → `pool=felhom` in the POST body; `Pool:""` → key absent. **Red-proof:** unconditional `v.Set("pool",…)` → the empty case sent `pool=` → omit assertion FAILED as designed; reverted. - `TestRestoreSitesUsePool` PASS: the provision bring-up threads `spec.Pool` and the restore-test hardcodes `DefaultPool` → both captured `RestoreLXCOptions.Pool == "felhom"`. **Red-proof:** removed `Pool` from both sites → the provision assertion FAILED; reverted. ## 5. Published + hub manifest - Gitea generic package **`admin/generic/felhom-agent/0.53.0/felhom-agent`**, sha256 `9d1ef8298dcc5090936afa1b88f1612c0b5f03a62548ebbe0989d34d6622aa61` (publish did delete-then-PUT + round-trip GET verify). - **Hub artifact manifest — REQUIRED operator follow-up (mandatory for fresh installs):** the served manifest still advertises agent **0.52.0**. A FRESH install now runs script v1.6.0 (scoped ACL + `Pool.Allocate`), which is only correct with agent ≥ v0.53.0 (restore `--pool`); with 0.52.0 the guest restores OUTSIDE the pool and the scoped token then 403s on it. **An operator must set the artifact manifest to agent 0.53.0 / sha `9d1ef8298dcc5090936afa1b88f1612c0b5f03a62548ebbe0989d34d6622aa61`** in the hub UI (now a dropdown, auto-sha — hub v0.29.0) before any fresh v1.6.0 install. The demo swap did NOT depend on this (binary deployed directly). ## 6. Supervised swap on felhom-pve — executed + validated (all gates PASS) Sequence: `systemctl stop felhom-agent` → `felhom-host-install.sh --rescope-acl --vmid 9201` (added the 3 scoped roles+grants incl. `/storage/felhom-pbs`; removed the old broad `FelhomAgent` `/` grant + role) → deploy `felhom-agent 0.53.0` (backup `.bak-0.52.0`) → `systemctl start`. Validation, all under the scoped token: | Gate | Result | |------|--------| | A selftest=read | ✅ OK — now sees only the pool guest (9201) + the 3 scoped storages (was 5+all guests) | | B provision-into-pool | ✅ `--selftest=bring-up -mode provision -vmid 9300` → boot+running, 9300 landed in pool (restore-into-pool + SetConfig + start all scoped) | | C **PBS under scope (residual #1)** | ✅ vzdump 9300 → `felhom-pbs` (200, task OK) + restore-from-PBS → 9301 pool=felhom (200, OK, landed in pool); cleanup 200 | | D **restore-test into pool (residual #2)** | ✅ scratch 990000 restored into pool, boot+running, torn down, `pass=true` | | E blast-radius | ✅ non-pool guests 9001/9100 → 403 (`VM.Audit`/`VM.PowerMgmt`); ungranted `felhom-usb` → 403 | | F 9201 health | ✅ running; controller container Up (healthy) — agent↔guest channel intact; `--selftest=hub` OK; journal clean; pool = `[9201]` | Cleanup: scratch 9300/9301/990000 destroyed, PBS + local scratch snapshots freed, `.bak-0.52.0` rollback binary retained. The live `felhom` pool holds only 9201. ## 7. NOT yet live-validated — awaiting supervised run - **The colleague's-box migration** (single-node, so no cluster nuance) — a later supervised repeat of the §6 stop→rescope→deploy→start + gates, once the hub manifest serves 0.53.0. ## 8. Observations - Storage blast-radius is a bonus: the scoped token also 403s on ungranted storages (felhom-usb) — read containment beyond guests. - The agent's `ListLXC`/`ListStorage` now return filtered lists (pool guests / scoped storages) — the host-report naturally scopes to Felhom's own footprint. Correct + intended. - felhom-pbs is the offsite backup target; the `Store` grant on `/storage/felhom-pbs` is mandatory (was the SPIKE's main unvalidated residual — now proven live).