5.9 KiB
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.goApply→m.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 genericif err != nil { … return }aborted BEFORE the root-run wrappergrant(only reached viaadopt/create) that creates that very ACL — a permanent self-deadlock. - Fix: on an
errors.As(err, *proxmox.APIError)withIsForbidden()ONLY, runfelhom-pbs-apply grant <id>now (root, no secret, no pre-existing entry —pveum acl modifyon a path is unconditional; verified inconfigs/felhom-pbs-applygrant)), re-read once, then flow the normal adoption/create path. Every other error stays transient. Grant-OK-but-still-403 →verify_failedloudly. The pre-check is KEPT (the spec's "don't drop it" — it short-circuits the happy path once the ACL exists). A benign extragranton the adopt path is idempotent. - Red-proof (run-fail-revert):
TestSelfGrant_PreCheck403DoesNotAbortBeforeGrant. Pre-fix (forcedif trueabort):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_idinto agent.json on convergence (manager.gofinishConverged→seedEscrowStorageID); the reader isinternal/localapi/escrow_ceremony.gohandleEscrowPreflight, 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 existingDRConfigured func() boolseam).cmd/felhom-agent/main.gowires it toconfig.Load(cfg.SourcePath).Escrow.PBSStorageID— exactly what the ceremony subprocess itself loads — so the row reflects the true state the bare--selftest=escrow-createone-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.goSetTrigger), 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—Listenerbinds a contentless UDP socket EXCLUSIVELY to the box's WG /32 (wgtunnel.LoadAssignedAddr, new helper readingregistered.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 wheneverwg_tunnel.enabled. Wired inmain.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) andTestDebounceCoalescesBurst(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;StorageActivewould 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-offsiteareFelhomAgentStoreforfelhom-agent@pve(user) andfelhom-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.