Files
felhom-agent/REPORT.md
T

7.1 KiB
Raw Blame History

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 — 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-offsite08: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 ≤23 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.