v0.89.0: pbsdr self-grant (R-22) + escrow config live-reload + agent-plane poke listener (Direction-2a)

- pbsdr: on a 403 pre-check (non-default storage id, no ACL yet) self-grant via the root wrapper then re-read, instead of aborting before the grant — closes F4/R-22. Red-proof TestSelfGrant_PreCheck403DoesNotAbortBeforeGrant.
- escrow preflight: late-bound CurrentPBSStorageID re-reads agent.json so a pbsdr-seeded pbs_storage_id flips the row green in-process (no restart). Red-proof TestEscrowPreflight_PBSStorageIDLiveReload.
- internal/poke: contentless UDP poke listener bound exclusively to the box WG /32 (port 51822), leading-edge debounced, fires the hub-loop out-of-band trigger for an immediate desired-state cycle. First slice of R-13. Red-proofs TestBindConfinement + TestDebounceCoalescesBurst.
This commit is contained in:
2026-07-16 22:47:22 +02:00
parent c040c180e9
commit a659e5dc09
11 changed files with 643 additions and 15 deletions
+38
View File
@@ -1,3 +1,41 @@
## v0.89.0 — agent train: PBS-DR self-grant (R-22) + escrow config live-reload + agent-plane poke listener (2026-07-16)
Three bundled agent-plane items. Green: `go build ./... && go vet ./... && go test ./...` all pass.
- **Item 1 — pbsdr self-grant (R-22, closes the F4 root cause from `tests/VALIDATION-n100`)**
(`internal/pbsdr/manager.go`): on a NON-DEFAULT PBS storage id the agent token holds no ACL on
`/storage/<id>` yet, so the reconcile tick's token-auth pre-check `StorageEntry` (GET
`/storage/<id>`) 403s and — pre-fix — aborted BEFORE the root-run wrapper `grant` that creates
that very ACL: a permanent self-deadlock that bit every legacy/descriptor-provisioned id (the
demo's `felhom-offsite`). Fix: on an `IsForbidden` pre-check error ONLY, run
`felhom-pbs-apply grant <id>` now (root, no secret, no pre-existing entry — `pveum acl modify` on a
path is unconditional), re-read once, then flow the normal adoption/create path. The pre-check is
KEPT (once the ACL exists it short-circuits the happy path cheaply); every other error stays
transient. Grant-then-still-failing surfaces `verify_failed` loudly, never a silent retry storm.
Red-proof: `TestSelfGrant_PreCheck403DoesNotAbortBeforeGrant` (fake 403 on the pre-check → pre-fix
never reaches the grant, calls=[] → FAIL; fixed → self-grant + converge, no secret consumed).
- **Item 2 — escrow config live-reload** (`internal/localapi/escrow_ceremony.go`,
`cmd/felhom-agent/main.go`): the pbsdr bridge seeds `escrow.pbs_storage_id` into agent.json on DR
convergence, but `/escrow/preflight` read a daemon-start snapshot and stayed red until a service
restart. New late-bound `EscrowCeremonyConfig.CurrentPBSStorageID` resolver (mirrors the existing
`DRConfigured func() bool` pattern) re-reads config from disk at preflight time — exactly what the
ceremony subprocess itself loads, so the row flips green in-process, no restart. Falls back to the
boot snapshot on read error / all-env config. Red-proof:
`TestEscrowPreflight_PBSStorageIDLiveReload` (seed after boot → pre-fix stays red → FAIL).
- **Item 3 — agent-plane poke listener (Direction-2a, `SPIKE-immediate-sync-transport-2026-07-16`)**
(new `internal/poke`, `internal/wgtunnel.LoadAssignedAddr`, `cmd/felhom-agent/main.go`,
`internal/hub/loop.go`): a CONTENTLESS UDP poke relayed hub → ep0 forced-command → wg0-origin →
this listener fires ONE immediate desired-state cycle (the hub control loop's out-of-band report
trigger — the SAME channel the storage watchdog uses; fan-in, cap-1 coalescing). The socket binds
EXCLUSIVELY to the box's WG /32 (`registered.json`; never 0.0.0.0, never the LAN iface) on the
fixed port **51822**; payload is ignored entirely (a forged/replayed poke costs at most one extra
debounced tick); leading-edge debounce (`DebounceWindow`) coalesces a burst into ≤1 tick. Enabled
whenever `wg_tunnel.enabled`; a lost poke is harmless (the 15-min cycle still reconciles). This is
the FIRST concrete slice of the OOB/mutual-repair arc (R-13) — the listener+trigger only, nothing
more. Red-proofs: `TestBindConfinement` (wildcard bind → bound to `::` → FAIL) and
`TestDebounceCoalescesBurst` (guard removed → 10 fires for 10 pokes → FAIL). Port registered in
REUSE.md as a shared cross-repo constant (hub sender + ep0 `felhom-poke`).
## v0.88.0 — controller-driven escrow ceremony: --output=json + localapi job + one-shot R claim (2026-07-13)
The agent half of the customer-facing recovery-code wizard (controller v0.127.0; every mechanism