# REPORT — operator-opt-in CPU/RAM cap for the provisioned guest (agent v0.52.0) **Date:** 2026-07-01 · **Repo:** `felhom-agent` · **Class:** Implementation (additive CLI→spec plumbing) Colleague-safety batch #3 (agent half). New `-cores`/`-memory` flags let an operator cap the provisioned guest's CPU cores + RAM **at provision time, before first boot**, so a trial appliance on a colleague's SHARED production Proxmox doesn't pressure his existing guests. Pure CLI→spec wiring — the reconcile engine already applied the cap. ## 1. Confirmed baseline | Repo | `main` @ commit | Current | → Target | |------|-----------------|---------|----------| | felhom-agent | `1502ca6` | **v0.51.0** | **v0.52.0** | ## 2. Files changed + commit - `cmd/felhom-agent/main.go` — `-cores`/`-memory` flags; `Cores`/`MemoryMB` on `bringUpSizing`; passed in both dispatch literals + both `BringUpSpec{}` builds (`runSelftestBringUp`, `runSelftestProvision`); `-selftest` usage string updated; version `0.51.0`→`0.52.0`. - `internal/reconcile/bringup_test.go` — new `TestBuildBringUpConfig_ResourceCaps`. - `internal/reconcile/bringup.go` — **not changed** (mechanism already correct; used only for the red-proof, reverted). - `CHANGELOG.md`, `README.md` — updated. - Commit **`0573ec9`** on `main` (parent `1502ca6`). ## 3. Green gate - **Local (Go 1.26.0, Windows)** and **build server 192.168.0.180 (Go 1.26.0, linux)**: `go build ./...` OK, `go vet ./...` OK. - `go test ./...` — **all packages pass EXCEPT a pre-existing flake** in `internal/escrow` (`TestGenerateRecoveryCode_EntropyAndFormat`): the random recovery code occasionally contains a hyphenated wordlist entry (e.g. "yo-yo"), so the test's `-`-split counts 11 "words" instead of 10. Confirmed flaky by re-running 5×: **1 fail / 4 pass**. It is in a package this task did NOT touch and is unrelated to the cap change (see Observations). My package `internal/reconcile` and everything else pass deterministically. ## 4. Test + omit-when-zero red-proof (§10) - `TestBuildBringUpConfig_ResourceCaps` **PASS**: `buildBringUpConfig(Cores:2,MemoryMB:4096)` → `cores="2"`,`memory="4096"`; `buildBringUpConfig(Cores:0,MemoryMB:0)` → both keys **absent**. - **Red-proof:** temporarily replaced the `if spec.Cores > 0 {…}` guard with an unconditional `params["cores"]=strconv.Itoa(spec.Cores)` → the omit-when-zero assertion FAILED as designed (`bringup_test.go:115: cores must be ABSENT when unset (golden default), got "0"`). Reverted; test passes again; `git diff` shows `bringup.go` clean (no net change). ## 5. Deployed version + T-A scratch live proof (felhom-pve) - Built `felhom-agent 0.52.0` on 180 (`sha256 5bfc690c421b7799…`); deployed to **felhom-pve** (backup `/usr/local/bin/felhom-agent.bak-0.51.0`, `install -m0755`, `systemctl restart`). Verified: `felhom-agent --version` = **0.52.0**, `systemctl is-active` = **active**, journal clean (normal host-report + sudo pct/docker activity). - **T-A (Scenario A, cap pre-boot) — PASS.** Ran the SAFE scratch bring-up on a free vmid **9300**: ``` felhom-agent --config /etc/felhom-agent/agent.json --selftest=bring-up -mode provision \ -archive local:backup/vzdump-lxc-9100-2026_06_27-11_42_51.tar.zst -vmid 9300 \ -cores 1 -memory 1024 -keep → bring-up record: Pass=true, Verified="boot+running", 18s pct config 9300: cores: 1 memory: 1024 rootfs: local-lvm:vm-9300-disk-0,size=32G pct destroy 9300 → DESTROYED (confirmed gone) ``` The guest booted+ran with `cores:1`/`memory:1024` = the cap was written into the pre-start config PUT (guest never ran uncapped). Scratch guest torn down; **guest 9201 untouched**. ## 6. Served/published + hub manifest - **Gitea generic package published:** `admin/generic/felhom-agent/0.52.0/felhom-agent`, sha256 `5bfc690c421b7799f4e0640f13559c41e5933ee60962bf80471eca7eae0e94cb`. Publish did delete-then-PUT + a round-trip GET sha verify (fetchable + intact end-to-end). Re-fetched independently: 12 964 463 bytes, sha `5bfc690c…`. - **Hub artifact manifest — NOT fully bumped (required operator follow-up).** The env fallback on the hub deploy was set to `ARTIFACT_AGENT_VERSION=0.52.0` / the new sha (live pod confirms). BUT the hub seeds env **only into an empty DB field** (`cmd/hub/main.go` L164 `if m.AgentVersion == ""`), and the stored manifest already holds **0.43.0**, which wins. `GET /api/v1/artifacts/{customer}` therefore still returns agent **0.43.0**. The only in-app editor is the **password-gated** operator UI (Configs → Day-0 artifacts / `handleSetArtifacts`), which I cannot submit; the alternative (a direct write to the live operator-backend DB — no `sqlite3` in the pure-Go hub image) is beyond this task's risk envelope. **ACTION REQUIRED before a colleague uses `--cores`/`--memory` on a fresh install:** an operator must set the artifact manifest to **agent 0.52.0 / sha 5bfc690c421b7799f4e0640f13559c41e5933ee60962bf80471eca7eae0e94cb** in the hub UI (the binary + env fallback are already in place). ## 7. NOT yet live-validated — awaiting supervised run - A **capped real customer `--selftest=provision`** (full slice-8A chain with `-cores`/`-memory` against a real customer/guest). The T-A scratch bring-up above is the safe mechanism proof; a real capped customer provision is the supervised step (colleague's box). ## 8. Observations (noticed, NOT acted on) - **Pre-existing flake** `internal/escrow/TestGenerateRecoveryCode_EntropyAndFormat` — the wordlist contains hyphenated entries (e.g. "yo-yo"); the test derives word count via `strings.Split(code,"-")`, so a hyphenated word inflates the count to 11 and the `== 10` assertion fails ~1/5 runs. Fix is a test/wordlist concern in a package outside this task's scope; left untouched. - The hub artifact manifest was already stale (0.43.0) before this task — fresh installs were fetching an old agent regardless. Bumping it (item 6) also resolves that lag.