Files
felhom-agent/REPORT.md
T

5.9 KiB

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 Applym.px.StorageEntry(ctx, id) (GET /storage/<id>, 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 <id> 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 finishConvergedseedEscrowStorageID); 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.PBSStorageIDexactly 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/pokeListener 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 :: / IsUnspecifiedFAIL; 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/<id>) 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 — CC-ownable done; remainder operator-gated)

  • Built + published agent 0.89.0 to Gitea (sha256 3969fd91…2609fb1a, round-trip verified) — the binary the floor-driven train (Scenario 4) fetches.
  • Scenario 4 / Scenario 1 live leg / Scenario 3 latency require the demo agent to self-update to 0.89.0 via the floor train, which needs the operator-password-gated Day-0 manifest vouch + floor raise (CC cannot drive that UI). Demo agent is still 0.88.0. Direct binary replace is forbidden by the spec (the floor train IS the validation), so these legs are the operator follow-up.
  • Scenario 1 rollback recorded FIRST (as required): current ACLs on the demo's /storage/felhom-offsite are FelhomAgentStore for felhom-agent@pve (user) and felhom-agent@pve!agent (token), propagate=1. Re-grant rollback = pveum acl modify /storage/felhom-offsite --users felhom-agent@pve --roles FelhomAgentStore + the --tokens 'felhom-agent@pve!agent' twin; the REVOKE that arms the deadlock is the same two with --delete. NOT run on 0.88.0 (pre-fix → no self-heal, would break the demo's pbsdr). The ep0 poke leg IS validated live — see the hub REPORT.

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.