Files
felhom-agent/REPORT.md
T

87 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<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` `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/<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-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 ≤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.