Files
felhom.eu/REPORT.md
T

4.3 KiB

felhom.eu — task reports

Overwrite this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in hub/CHANGELOG.md; the scripts history lives in scripts/CHANGELOG.md.

Direction-2a agent-plane immediate-sync — poke sender + ep0 felhom-poke surface (hub v0.59.0) — 2026-07-16

Baseline: hub v0.58.0 → v0.59.0 (LIVE on prod, ArgoCD Synced/Healthy). Pairs with felhom-agent v0.89.0 (the poke listener). Green gate go build ./... && go vet ./... && go test ./... passes. Design contract: documentation/audits/SPIKE-immediate-sync-transport-2026-07-16.md option (a).

What shipped

  • internal/poke — the SSH poke SENDER (third sibling of internal/wgsync/internal/tenantsync): a pinned-host-key (algorithm-pinned ssh.FixedHostKey) in-process client reusing the peersync endpoint + host key, its OWN forced-command key. Client.Poke(ctx, boxWGIP) refuses any target outside 10.77.0.0/24 BEFORE dialing, then SSHes to ep0's felhom-poke with the box WG /32 as the command string (→ $SSH_ORIGINAL_COMMAND) which sends one empty UDP datagram to <ip>:51822. Notifier.PokeHost/PokeAllHosts are fire-and-forget (detached goroutine, nil-receiver-safe) — a poke never blocks or fails the operator save; a missing peer / SSH error is logged, the report cycle reconciles. Tests: resolved-IP send, no-peer / store-error no-send, nil no-op, Poke pre-dial non-WG refusal.
  • Wiring: Server.SetPoke; applyPBSDR fires PokeHost(host.HostID) after each generation-bumping descriptor save; handleSetArtifacts (MinAgent-floor save) fires PokeAllHosts(). cmd/hub/main.go env-configures the sender (POKE_SSH_KEY_FILE, reusing WG_ENDPOINT_SSH_ADDR/_HOSTKEY/user).
  • ep0 surface: scripts/felhom-poke.sh v1.0.0 — a NON-root (felhom-peersync, no sudoers grant) forced command that validates $SSH_ORIGINAL_COMMAND to the WG /24 and sends one empty datagram from wg0. Contentless + confined (the WG kernel refuses non-peer /32s — spike P1 EKEYREJECTED). Runbook documentation/runbooks/offsite-endpoint.md §11. Port 51822 = shared cross-repo constant.
  • manifests/hub.yaml: POKE_SSH_KEY_FILE env + optional Secret/agent-poke mounted 0400; image tag 0.58.00.59.0.

Landmarks as found at source

  • Poke wiring is host-keyed (store.GetWGPeerForHost → the box WG /32), the sibling of the customer-keyed internal/intent long-poll (Direction-2b / controller plane). SetHostDesired bumps the poked host's generation; the poked immediate report carries the advanced envelope generation → the agent fetches the new descriptor.
  • Spec-vs-source note (source wins): the MinAgent-floor / artifact-manifest save (handleSetArtifacts) does NOT itself bump per-host desired generation — the agent self-update dispatches via signed-ops on the next report — so PokeAllHosts() there accelerates the next report cycle where the floor is applied, rather than delivering a desired-state delta. Kept the poke there (the spec names this handler) and documented the semantics.

Live validation

  • Deployed: hub 0.59.0 built + pushed on 180, manifest tag bumped + ArgoCD synced (Synced/Healthy, rollout complete, image :0.59.0); after the Secret was created it logs agent-plane poke enabled.
  • ep0 poke surface installed live (additive; felhom-peersync authorized_keys 2→3 lines, peersync/tenantsync UNCHANGED; /usr/local/bin/felhom-poke 0755; Secret/agent-poke holds the private key, the build-server copy shredded).
  • ep0 leg live-validated with the poke key: a valid WG target (10.77.0.2) → poke-fired; 8.8.8.8 AND rm -rf / as the SSH command → refused non-WG target (the forced command NEVER executes client input, only reads it as an IP candidate). This proves the full hub→ep0→box datagram path and the credential-theft blast radius (≤ "make a registered box tick").
  • Not yet fired end-to-end from an operator save (the config/floor save UIs are password-gated; CC can't log in) and the box-side tick awaits the demo agent reaching v0.89.0 via the floor train (operator vouch) — see the felhom-agent REPORT. Scenario 3's measured save-to-tick latency is the operator follow-up.