Files
felhom-agent/REPORT.md
T

99 lines
6.5 KiB
Markdown

# REPORT — felhom-agent v0.90.0 (agent train: guest RAM resize R-24 + fast-tick R-28)
**Date:** 2026-07-17 · **Baseline:** v0.89.0 (`9127f54`) → **v0.90.0** (`ac112c9`, pushed) · **MinAgent coupling:** felhom-controller v0.143.0 gates its guest-memory-resize UI on this agent.
## 1. What shipped
Two ruled features on one train (Viktor's one-train/one-floor ruling), plus a diagnosis passenger.
- **R-24 guest RAM resize (controller-direct)** — new self-scoped local-API surface `GET`/`POST /guest/memory`
(`internal/localapi/guestmemory.go`). The agent is the security boundary: bounds recomputed FRESH per
request from a live read; applies via PVE `SetConfig` (live cgroup apply, no reboot); verify-after-apply.
- **R-28 fast-tick-until-first-convergence** — `internal/fasttick/`, wired over four cached sources; pulses
the shared out-of-band trigger every 30 s while unapplied, self-disarms on convergence. Seams added:
`reconcile.Engine.LastResult()`, `wgtunnel.Manager.TunnelConvergence()`.
- **Guests-0/0** — diagnosed live, hypothesis REFUTED (no code change).
## 2. Phase-0 probe (the gate — PASSED)
Prerequisite: prove `PUT /nodes/{node}/lxc/{vmid}/config memory=X` hot-applies under the AGENT token. Run on
the nested demo box `demo-vm-felhom-4846bc` (hostname `felhom-appliance`, guest LXC 9201) — reached via the
hub-vaulted G1 break-glass root credential (the box's root PW was unknown; retrieved operator-side from the
hub SQLite `host_recovery` table, then shredded). Transcript:
```
node=felhom-appliance vmid=9201 token_user=felhom-agent@pve
BEFORE config.memory=2890 MB | status.maxmem=3030384640 B (2890 MB) | status=running | host-uptime=8226s
BEFORE guest MemTotal: 2959360 kB | guest /proc/uptime=8227.76
--- PUT memory=3146 (agent token) --- PUT data field: None (empty == synchronous apply, no UPID)
AFTER config.memory=3146 MB | status.maxmem=3298820096 B (3146 MB) | status=running | host-uptime=8229s
AFTER guest MemTotal: 3221504 kB | guest /proc/uptime=8231.54
VERDICT maxmem_grew=True guest_running=True guest_not_rebooted=True
REVERT memory=2890 → maxmem 3030384640 B (2890 MB), running; guest MemTotal 2959360 kB
```
Proven: live cgroup apply (maxmem moves, guest running), **no reboot** (guest /proc/uptime keeps climbing),
synchronous (no UPID), agent token has the privilege (no 403), and **/proc/meminfo ripples via lxcfs** — the
mechanism that makes the controller's deploy-page memory math follow for free. The gate PASSED → the resize
leg was implemented.
## 3. Files created / modified
- `internal/localapi/guestmemory.go` (new) + `guestmemory_test.go` (new); `internal/localapi/server.go`
(Options.Memory + fields + routes); `cmd/felhom-agent/main.go` (wire Memory=px; construct + launch fasttick).
- `internal/fasttick/fasttick.go` + `fasttick_test.go` (new).
- `internal/reconcile/engine.go` (`LastResult`/`recordResult`) + `lastresult_test.go` (new).
- `internal/wgtunnel/manager.go` (`TunnelConvergence` + cached snapshot refresh in `Apply`).
- `CHANGELOG.md`, `REPORT.md` (this), `CONTEXT.md`, `REUSE.md`.
## 4. Tests + red-proofs (all restored green)
- localapi memory: GET fields, grow, shrink-above-floor, the three refusals (each asserting **SetConfig
count == 0**), cross-guest 403, fresh-bounds-per-request, verify-not-reflected 502, nil-config 503.
- fasttick: pulse-while-unconverged, silent-when-converged, the ruled disarm-on-convergence, full-channel
non-blocking drop, first-reason. reconcile: `LastResult` effect + pre-first-run ok=false.
- **Red-proofs (run-fail-restore):** (i) floor guard removed → below_usage_floor 412 test fails; (ii) max
guard removed → above_max test fails; (iii) fast-tick ignores convergence (always pulse) → the silent +
disarm tests fail. All confirmed red, then restored. Full suite: `go build ./... && go vet ./... &&
go test ./...` — all 28 packages pass.
## 5. Guests-0/0 diagnosis — hypothesis REFUTED, no fix
Live read-only checks on the nested box: pool `felhom` `members: [9201]`; the agent token's ListLXC sees
`[9201]` (VM.Audit comes from the `/pool/felhom` grant — so pool membership IS the visibility mechanism);
hub reports 1/1. The agent log shows the observed 0/0 was the **pre-provision window**: `16:11 stale-lock …
pool=felhom listed=0` + `desired … guests=0`, then `16:16 lanresolver … vmid=9201` (guest created). No guest
existed yet → empty list, no error (the silent shape) — correct, not a bug. The existing `PoolAddVMID`
re-assert (`bringup.go:498`) already covers the known restore-over-existing edge (campaign-2 R2). **Item 2's
fast-tick is precisely the mitigation** — it shortens the pre-provision reporting window from ≤15 min to ~30 s.
## 6. Deploy verification (BOTH hosts)
Built on 180 (`go build -ldflags -X main.version=0.90.0`), deployed to both Proxmox hosts (backup + install +
restart):
- **felhom-pve** (the N100): `felhom-agent 0.90.0` active; `capabilities self-check ok=63 total=63 degraded=0`;
**`fast-tick armed: 30s out-of-band cadence while desired-state is unapplied`**; poke listener + local-api up.
- **demo-vm-felhom-4846bc** (nested, 192.168.0.135): `felhom-agent 0.90.0` active; `capabilities self-check
ok=63 degraded=0`; `fast-tick armed: 30s …`; local-api on 192.168.0.135:8443. (Converged box → fast-tick
quiet after the startup line, as designed.)
## 7. NOT yet live-validated / operator GO
- **Fast-tick real-onboarding proof:** the fast-tick's value shows on a REAL fresh box's pre-tunnel window
(the rehearsal S5 bind sequence should now be visibly instant). Not manufacturable non-destructively here;
both agents show the armed startup line + the unit tests prove the pulse/disarm.
- **Floor LAST (operator GO):** both demo agents verifiably run 0.90.0 (the safety gate) and the controller
coupling is PROVEN LIVE via the X-Felhom-Agent-Version header (see the controller REPORT's live refusals).
Publishing 0.90.0 to Gitea + the hub Day-0 manifest **vouch + MinAgent-floor raise to 0.90.0** are set via
the operator-password-gated Day-0 manifest UI (CC cannot log in) — the operator (Viktor) GO step, matching
the v0.89 train's "publish/vouch = operator follow-up".
## 8. Observations
- The nested demo host is tiny (4 GB): its resize bounds are degenerate (`max_mb 1866 < min_mb 2048 <
current 2890`) — no resize *succeeds* through the bounded endpoint there (all refused correctly). The
successful apply is Phase-0-proven at the agent layer; the controller live-validation exercised the
refusals (above_max, below_min) end-to-end.
- REUSE.md gained the fasttick Source pattern + the localapi MemoryOps seam.