v0.90.0 — guest RAM resize (R-24) + fast-tick-until-convergence (R-28)

MinAgent coupling: felhom-controller v0.143.0 gates its guest-memory-resize UI on
this agent (FeatureGuestMemoryResize, MinAgent 0.90.0).

R-24 guest RAM resize (internal/localapi/guestmemory.go): self-scoped GET/POST
/guest/memory. Agent enforces every bound FRESH per request (min 2048, max
host_total-2048, shrink floor max(2048, usage+512)); applies via PVE SetConfig —
live cgroup apply, no reboot (Phase-0 proven on the nested demo box). Verify-after-apply
re-reads maxmem before claiming success. New narrow MemoryOps seam (GuestAPI untouched);
Options.Memory nil -> 503. Memory only.

R-28 fast-tick (internal/fasttick): while any desired-state item is unapplied -
including the pre-tunnel window a hub poke can't reach - pulse the shared out-of-band
trigger every 30s, self-disarm on convergence. Four cached sources (desired-gen==0,
reconcile Planned-Pending>0, pbsdr waiting_secret only, wgtunnel desired-not-operational);
LOUD pbsdr states + pending_signature excluded. Seams: reconcile.Engine.LastResult() +
wgtunnel.Manager.TunnelConvergence() (cached, no per-tick exec).

Guests-0/0: hypothesis REFUTED live (9201 IS a pool member; 0/0 was the pre-provision
window; PoolAddVMID re-assert already covers restore-over-existing). No code change; the
fast-tick mitigates the window.

Tests + red-proofs (i floor guard, ii max guard, iii always-pulse) all restored green.
This commit is contained in:
2026-07-17 19:09:40 +02:00
parent 9127f547f9
commit ac112c956e
10 changed files with 958 additions and 1 deletions
+45
View File
@@ -1,3 +1,48 @@
## v0.90.0 — agent train: guest RAM resize (R-24) + fast-tick-until-convergence (R-28) (2026-07-17)
**MinAgent coupling:** felhom-controller v0.143.0 gates its guest-memory-resize UI on this agent
(`FeatureGuestMemoryResize`, MinAgent 0.90.0). One train, one floor raise (Viktor's ruling).
Green: `go build ./... && go vet ./... && go test ./...` all pass.
- **Item 1 — guest RAM resize (R-24, controller-direct)** (`internal/localapi/guestmemory.go`, new):
a new self-scoped local-API surface — `GET /guest/memory` (current allocation, live usage, and the
enforced bounds, all agent-computed in MB) and `POST /guest/memory` (bounded resize). The agent is
the security boundary: every bound is recomputed FRESH per request from a live read (the UI's numbers
are decoration). Ruled bounds — min **2048 MB**; max **host_total 2048 MB** (host reserve); a SHRINK
is refused below **max(2048, usage + 512 MB)** with `below_usage_floor` (stop apps first). Applies via
the PVE API `SetConfig` — a **live cgroup apply, no reboot** (Phase-0 PROVEN on the nested demo box:
maxmem moves with the guest running, `/proc/meminfo` ripples via lxcfs). Verify-after-apply: the new
maxmem is re-read and must equal the target before success is claimed (a pending/reboot outcome is a
502, never a false success). Refusals return a machine `code` (below_min/above_max/below_usage_floor)
+ fresh bounds at 412; SetConfig is never called on a refusal path. Single-flight per host. Optional
`Options.Memory` (nil → 503 "not configured"); a NEW narrow `MemoryOps` interface (does not touch the
shared `GuestAPI`). Wired in `main.go` from the existing proxmox client. Memory only; cores stay
observation.
- **Item 2 — fast-tick-until-first-convergence (R-28)** (`internal/fasttick/`, new): the agent-plane
immediacy SECONDARY. While ANY desired-state item is unapplied — most importantly the **pre-tunnel
WG-registration window a hub poke cannot reach** — it pulses the SAME out-of-band report trigger the
watchdog/poke use, every **30 s**, and **self-disarms emergently** the instant everything converges.
State-based by ruling: nothing to journal, no timer to leak. Four cached sources (no exec/network per
tick): desired-generation == 0 (never fetched); reconcile `LastResult` actionable drift
(`Planned Pending > 0` — a destructive `pending_signature` is EXPECTED, excluded); pbsdr
`waiting_secret` ONLY (the LOUD `consumed_failed`/`verify_failed` are excluded so a stuck box never
hammers); wgtunnel block-desired-but-not-operational. Supporting seams: `reconcile.Engine.LastResult()`
(mutex-recorded per pass) and `wgtunnel.Manager.TunnelConvergence()` (cached snapshot refreshed at each
Apply — the fast-tick never execs `wg`/`systemctl`). A perma-unconverged box fast-ticks at ~2 small
reports/min (bounded, documented; timers were deliberately rejected).
- **Guests-0/0 (diagnosis passenger):** REFUTED the pool-membership hypothesis on the live nested box —
guest 9201 IS a pool member, the agent token sees it (VM.Audit comes from the `/pool/felhom` grant),
hub reports 1/1. The observed 0/0 was the legitimate **pre-provision reporting window** (no guest
existed yet); 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 exactly the window's mitigation.
No code change.
- **Tests + red-proofs (run-fail-restore):** localapi memory (grow/shrink, the three refusals each with
a **SetConfig-count == 0** assertion, cross-guest 403, fresh-bounds-per-request, verify-not-reflected
502, nil-config 503) — red-proofs (i) floor guard removed → below_usage_floor fails; (ii) max guard
removed → above_max fails. fasttick (pulse-while-unconverged, silent-when-converged, the ruled
disarm-on-convergence, full-channel non-blocking drop, first-reason) — red-proof (iii) always-pulse →
the silent + disarm tests fail. `Engine.LastResult` effect + pre-first-run ok=false. All restored green.
## v0.89.0 — agent train: PBS-DR self-grant (R-22) + escrow config live-reload + agent-plane poke listener (2026-07-16)
Three bundled agent-plane items. Green: `go build ./... && go vet ./... && go test ./...` all pass.