# REPORT — felhom-agent v0.89.0 (agent train: R-22 self-grant + escrow live-reload + poke listener) **Date:** 2026-07-16 · **Baseline:** v0.88.0 (`c040c18`) → **v0.89.0** (`a659e5d`, pushed) **Green gate:** `go build ./... && go vet ./... && go test ./...` — all pass (full suite, local go1.26). **Design contract for item 3:** `felhom.eu/documentation/audits/SPIKE-immediate-sync-transport-2026-07-16.md`. ## Items + landmarks as found at source ### Item 1 — pbsdr self-grant (R-22, closes F4 from tests/VALIDATION-n100-baremetal) - **Where the pre-check lives:** `internal/pbsdr/manager.go` `Apply` → `m.px.StorageEntry(ctx, id)` (GET `/storage/`, token-auth). On a non-default storage id the agent token has no ACL there yet → **403**; pre-fix the generic `if err != nil { … return }` aborted BEFORE the root-run wrapper `grant` (only reached via `adopt`/create) that creates that very ACL — a permanent self-deadlock. - **Fix:** on an `errors.As(err, *proxmox.APIError)` with `IsForbidden()` ONLY, run `felhom-pbs-apply grant ` now (root, no secret, no pre-existing entry — `pveum acl modify` on a path is unconditional; verified in `configs/felhom-pbs-apply` `grant)`), re-read once, then flow the normal adoption/create path. Every other error stays transient. Grant-OK-but-still-403 → `verify_failed` loudly. The pre-check is KEPT (the spec's "don't drop it" — it short-circuits the happy path once the ACL exists). A benign extra `grant` on the adopt path is idempotent. - **Red-proof (run-fail-revert):** `TestSelfGrant_PreCheck403DoesNotAbortBeforeGrant`. Pre-fix (forced `if true` abort): `self-grant never ran … calls=[]` → **FAIL**. Fixed: self-grant runs, converges (adopted), no secret consumed, 2 StorageEntry calls (403 + post-grant re-read) → **PASS**. ### Item 2 — escrow config live-reload - **Two ends:** the pbsdr bridge seeds `escrow.pbs_storage_id` into agent.json on convergence (`manager.go` `finishConverged`→`seedEscrowStorageID`); the reader is `internal/localapi/escrow_ceremony.go` `handleEscrowPreflight`, which read a **daemon-start snapshot** (`EscrowCeremonyConfig.PBSStorageID`) → stayed red until a service restart. - **Reload strategy chosen (stated per source shape):** *the preflight reads current disk state*, via a new late-bound `CurrentPBSStorageID func() string` (mirrors the existing `DRConfigured func() bool` seam). `cmd/felhom-agent/main.go` wires it to `config.Load(cfg.SourcePath).Escrow.PBSStorageID` — **exactly what the ceremony subprocess itself loads** — so the row reflects the true state the bare `--selftest=escrow-create` one-liner will see. Falls back to the boot snapshot on read error / all-env config. Preferred over an in-memory push (no cross-component coupling, single source of truth = disk). - **Red-proof:** `TestEscrowPreflight_PBSStorageIDLiveReload` — seed after boot, same process; pre-fix (static field) stays red → **FAIL**; fixed flips green + reports the seeded id → **PASS**. ### Item 3 — agent-plane poke listener (Direction-2a) - **Trigger fired:** the hub control loop's out-of-band report trigger (`internal/hub/loop.go` `SetTrigger`), the SAME cap-1 non-blocking channel the storage watchdog uses — fan-in, coalescing. An immediate collect→report cycle carries the advanced envelope generation → the desired-state syncer fetches → pbsdr/wg consumers apply. - **New `internal/poke`** — `Listener` binds a contentless UDP socket EXCLUSIVELY to the box's WG /32 (`wgtunnel.LoadAssignedAddr`, new helper reading `registered.json`; never 0.0.0.0, never LAN) on the fixed **port 51822**; payload ignored; leading-edge debounce (`DebounceWindow`) coalesces a burst into ≤1 tick; enabled whenever `wg_tunnel.enabled`. Wired in `main.go` (goroutine + errc 7→8). - **Red-proofs (run-fail-revert):** `TestBindConfinement` (wildcard bind → bound to `::` / `IsUnspecified` → **FAIL**; fixed → the specific WG addr, datagram fires the trigger) and `TestDebounceCoalescesBurst` (guard removed → 10 fires for 10 pokes → **FAIL**; fixed → 1, then fires again after the window). Port registered in REUSE.md as a shared cross-repo constant. ## Deviations from the spec's landmark guesses (source wins) - The 403 pre-check is `StorageEntry` (GET `/storage/`) exactly as ROADMAP R-22 states — no deviation; `StorageActive` would also 403 but the self-grant at the top fixes both. - (Hub side, cross-repo) the MinAgent-floor save does not itself bump per-host desired generation — noted in the hub REPORT. ## Live validation (Phase 5 — ALL THREE LEGS PROVEN LIVE on the demo, 2026-07-17) - **Built + published** agent 0.89.0 to Gitea (sha256 `3969fd91…2609fb1a`, round-trip verified). - **Scenario 4 — floor-driven A/B train (0.88→0.89), PROVEN LIVE.** Operator vouched 0.89.0 + raised MinAgent to 0.89.0, then signed+enqueued the `agent_update` op (the self-update trigger — a per-box operator-signed op; the vouch+floor alone does NOT queue it; CC cannot sign, per DRILL-GL6). The box fetched it on its next report and self-updated: `gate decision class=agent_update allowed=true reason=signed key_id=felhom-op-1` → download (sha `3969fd91…` verified) → `felhom-selfupdate-guarded apply` (root A/B flip) → new daemon `version=0.89.0` → 60 s dwell → `selfupdate: update committed` (no rollback). Poke listener bound clean: `poke: listening … addr=10.77.0.2:51822`; 63/63 caps. - **Scenario 1 — R-22 self-heal, PROVEN LIVE.** Rollback re-grant recorded FIRST (`pveum acl modify /storage/felhom-offsite --users felhom-agent@pve --roles FelhomAgentStore` + the `--tokens 'felhom-agent@pve!agent'` twin — delete syntax is `pveum acl delete`). **Faithful F4 reproduction:** moved the pbsdr marker aside (the reinstall's fresh-state — revoke-alone is a no-op because the `applied` marker short-circuits Apply before any storage read; an important reproduction finding) + `pveum acl delete` both felhom-offsite ACLs. Next reconcile tick: `08:24:16 pbsdr: pre-check 403 … self-granting via the root wrapper … (R-22) storage_id=felhom-offsite` → `08:24:19 pbsdr: converged state=adopted` — **~3 s, no operator action**. Verified: ACLs self-restored (user+token), marker rewritten (adopted), `pvesm status felhom-offsite` = **active**. Pre-fix = permanent 403→abort loop. - **Scenario 3 — poke → tick latency, MEASURED LIVE.** ep0 send `06:28:55.765` UTC → box `08:28:55.796` `poke received → triggering an immediate desired-state cycle from=10.77.0.1` → same instant `out-of-band report triggered (watchdog/poke)`. **~31 ms** ep0→box over the tunnel (spike ~29 ms), sub-ms to the report cycle; WG-confined (from `10.77.0.1`, to the `10.77.0.2`-bound socket). The hub-save→poke fire is separately live-proven (hub REPORT: real manifest save → `sync-poke delivered to 10.77.0.2`); full save→tick ≈ ~0.45 s (SSH-dominated), well under the ≤2–3 s target. ## R-13 Item 3 is the FIRST concrete slice of the OOB/mutual-repair arc (the listener + trigger only). R-13 stays open; nothing beyond the poke channel was built.