# 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](hub/CHANGELOG.md); the scripts history lives in [scripts/CHANGELOG.md](scripts/CHANGELOG.md). ## Hub v0.63.0 — system-initiated immediacy (poke + intent bump at every mutation site) — 2026-07-17 ### 1. Baseline used - **felhom.eu** @ `4c9b0e87063278cddbe51e3966a8f8592c7c18f2` ("docs: R-21 slice C …", 2026-07-17), clean tree on `main`, local == `origin/main` re-confirmed before starting. - **Hub** v0.62.0 → **v0.63.0**. Deploy manifest `manifests/hub.yaml` image line `felhom-hub:0.62.0` → `:0.63.0`. - Both immediate-sync transports were already PROVEN-LIVE (agent poke: agent v0.89.0 + hub v0.59.0, 2026-07-17; intent wait channel: hub v0.58.0 / controller v0.140.0). This task is **call-site wiring only** — no new mechanism, no spike. ### 2. Files created / modified **Implementation (commit `30972d8`):** - `hub/internal/web/pbsdr.go` — +3 pokes: `PBSDRAutoProvision`, `ReissuePBSDR`, `handlePBSDRReissue` (each after the successful `SetHostDesired`). - `hub/internal/api/handler.go` — new nil-safe `Poker` interface + `poker` field + `SetPoker`; poke in `handleAdminSetDesiredState`; intent `Bump` in `reissueOnReenroll`. - `hub/internal/api/wg.go` — `PokeAllHosts` in `handleAdminSetOperatorPeer` (only on a successful `BumpAllHostGenerations`). - `hub/cmd/hub/main.go` — capture the one `poke.Notifier` and wire it to BOTH planes (`webServer.SetPoke(n)` + `apiHandler.SetPoker(n)`); startup log "web + api admin seams armed". - `hub/internal/web/pbsdr_poke_test.go` (new) — 5 web tests (Groups A, B). - `hub/internal/api/poke_seam_test.go` (new) — 5 api tests (Groups C, D). - `hub/CHANGELOG.md` — v0.63.0 entry (newest on top). - `REUSE.md` — new canonical-pattern row: "Immediate-sync notify (per plane)". **Docs (commit `50c090e`):** - `REPORT.md` (this file, overwritten), `CONTEXT.md` (decision recorded), `documentation/architecture/00-capability-map.md` (immediacy row note + version), `documentation/backlog/ROADMAP.md` (new R-28: agent fast-tick SECONDARY). **Manifest (commit `587fcc2`):** `manifests/hub.yaml` image tag → `felhom-hub:0.63.0`. ### 3. Per-commit hashes pushed to `main` | # | Hash | Contents | |---|------|----------| | 1 | `30972d8` | code + tests + CHANGELOG + REUSE | | 2 | `50c090e` | REPORT + CONTEXT + capability-map + ROADMAP | | 3 | `587fcc2` | manifests/hub.yaml image bump → 0.63.0 | ### 4. Test results + red-proof outcomes **New tests (10), all green:** | Test | Group | Asserts | |------|-------|---------| | `TestPBSDR_AutoProvisionPokes` | A | auto-provision success → exactly one poke to the resolved WG /32 (10.77.0.2) within 2 s | | `TestPBSDR_AutoProvisionBlockedDoesNotPoke` | A (neg) | blocked precondition (no WG peer) → zero pokes | | `TestPBSDR_ReissueCorePokes` | B1 | `ReissuePBSDR` success → one poke to the host's /32 | | `TestPBSDR_ReissueCoreErrorDoesNotPoke` | B (neg) | tenantsync-error reissue → zero pokes (fire-after-commit) | | `TestPBSDR_HandleReissuePokes` | B2 | operator button success → one poke | | `TestAdminSetDesiredState_PokesTargetHostOnly` | C1 | admin-set success → PokeHost==1 (target only), PokeAllHosts==0; invalid-JSON path → 0 | | `TestAdminSetDesiredState_NilPokerNoPanic` | C1 (nil) | nil poker → mutation still 200, no panic | | `TestAdminSetOperatorPeer_PokesFleet` | C2 | operator-peer success → PokeAllHosts==1, PokeHost==0 | | `TestReenroll_BumpsIntent` | D | re-enroll → intent generation advances (synchronous) | | `TestReenroll_NilIntentHubNoPanic` | D (nil) | nil intent hub → enroll still 201, no panic | Async web assertions synchronize on a buffered channel in the fake sender with a bounded `select` (≤2 s), never sleep-poll. The web fake sender structurally satisfies poke's unexported `pokeSender` seam (value passed, type never named); the notifier is a REAL `poke.Notifier` over the test store — exactly as `main.go` builds it. The api fake `Poker` satisfies the exported `api.Poker` interface and counts calls synchronously. **Red-proofs (run-fail-restore), one representative per scenario group:** | Group | Removed | Test | Result | |-------|---------|------|--------| | A | `PBSDRAutoProvision` poke (Part 1.1) | `TestPBSDR_AutoProvisionPokes` | FAILED red ("no poke within 2s… 10.77.0.2") → restored | | B | `ReissuePBSDR` poke (Part 1.2) | `TestPBSDR_ReissueCorePokes` | FAILED red ("no poke within 2s… 10.77.0.2") → restored | | C | `handleAdminSetDesiredState` poke (Part 2.2) | `TestAdminSetDesiredState_PokesTargetHostOnly` | FAILED red ("PokeHost targets = [], want [h1]") → restored | | D | `reissueOnReenroll` bump (Part 3) | `TestReenroll_BumpsIntent` | FAILED red ("intent generation = 0, want > 0") → restored | These tests fail on the pre-fix tree by construction; a representative removal per group was demonstrated (B2 and C2 share their group's wiring shape with the demonstrated B1/C1 removals). ### 5. Test count before/after - Package `internal/web`: +5 tests (new `pbsdr_poke_test.go`). - Package `internal/api`: +5 tests (new `poke_seam_test.go`). - Full suite after: `go build ./... && go vet ./... && go test ./...` — **all packages green** (api + web are the only two exercising the new code; every other package unchanged). ### 6. Deployed version + pod/log verification Built on 180 from `~/git/felhom.eu` @ `50c090e` (build.sh `git pull --ff-only` confirmed the wiring is present in the built source) → `gitea.dooplex.hu/admin/felhom-hub:0.63.0` pushed. Manifest bump (`587fcc2`) → ArgoCD app `felhom` hard-refresh (revision `587fcc27ca03`, OutOfSync) → deliberate sync: ``` sync=Synced health=Progressing deployment "hub" successfully rolled out image: gitea.dooplex.hu/admin/felhom-hub:0.63.0 # startup log (poke ENABLED — POKE_SSH_KEY_FILE Secret present in this environment): [INFO] felhom-hub 0.63.0 starting [INFO] agent-plane poke enabled (endpoint 167.233.158.164:22, user felhom-peersync; web + api admin seams armed) ``` The startup line is the new one — "web + api admin seams armed" confirms `apiHandler.SetPoker(n)` was wired alongside `webServer.SetPoke(n)` over the one notifier. Poke is enabled (not the disabled fallback), so all wired sites are armed live. ### 7. NOT yet live-validated (real system-initiated events only) The new pokes/bumps fire only on real system events, none manufacturable non-destructively from this task. Acceptance bar here = **unit tests + deploy + startup-log verification**. Unfired live legs, to be observed at the rehearsal's bind sequence (`RUNBOOK-rehearsal-v3 S5`): - **Auto-provision poke** — fires when a DR-ON customer's host registers its WG peer for the first time and the atom provisions (the exact observed slice-C lag). - **Reconciler-escalation poke** — fires when the pbsdrheal reconciler escalates to `ReissuePBSDR` (a box stuck in `waiting_secret`/`consumed_failed` after losing its converged marker). - **Clean-slate re-enroll bump** — fires when a wiped, re-minted box re-enrolls for a claimed/offsite customer (on that path the controller usually does not exist yet; its startup fetch covers that shape). - **Operator-button / admin-api pokes** (`handlePBSDRReissue`, admin desired-state / operator-peer) — the operator-triggered legs; exercisable in a supervised session but not fired here. The **first-hold-baseline race** stays OPEN by design: a bump landing during a fresh controller's FIRST wait is recorded as baseline without firing (the ruled fix is carrying `intent_gen` in the report ACK — its own future task, out of scope). ### 8. Part-4 audit table — EVERY desired-state mutation site Re-grepped at implementation time for all non-test callers of `SetHostDesired`, `BumpHostDesired`, `BumpAllHostGenerations`, plus the system-initiated customer-deliverable writers. No forgotten fourth caller — every site below is accounted for. | Site (file:landmark) | Initiator | Plane | Notification after this task | Reason if none | |---|---|---|---|---| | `web/pbsdr.go` `applyPBSDR` disable (~L143→148) | operator (form) | agent | poke (pre-existing) | — | | `web/pbsdr.go` `applyPBSDR` re-enable/storage-id (~L168→173) | operator (form) | agent | poke (pre-existing) | — | | `web/pbsdr.go` `applyPBSDR` fresh-provision (~L179→186) | operator (form) | agent | poke (pre-existing) | — | | `web/pbsdr.go` `pbsdrProvisionAtom` `SetHostDesired` (~L273) | — (shared atom) | agent | poke at each caller | callers poke: `applyPBSDR` (L186) + `PBSDRAutoProvision` (NEW) | | `web/pbsdr.go` `PBSDRAutoProvision` (~L312) | **system** (WG-reg hook) | agent | **NEW poke** | — (the observed lag) | | `web/pbsdr.go` `ReissuePBSDR` `SetHostDesired` (~L357) | **system** (reconciler escalation) | agent | **NEW poke** | — (also covers pbsdrheal, zero reconciler change) | | `web/pbsdr.go` `handlePBSDRReissue` `SetHostDesired` (~L409) | operator (button) | agent | **NEW poke** | — | | `api/handler.go` `handleAdminSetDesiredState` `SetHostDesired` (~L1419) | operator/system (admin api) | agent | **NEW poke** (`PokeHost`) | — | | `api/wg.go` `handleAdminSetOperatorPeer` `BumpAllHostGenerations` (~L504) | operator (admin api) | agent | **NEW poke** (`PokeAllHosts`, fire-after-commit) | — | | `api/wg.go` `handleRegisterWGPeer` `BumpHostDesired` (~L291) | system (agent registration) | agent | **none (deliberate)** | box's tunnel does not exist until it fetches the WG block — a poke is undeliverable by construction; the agent fast-tick SECONDARY (R-28) owns this leg | | `api/wg.go` WG peer delete `BumpHostDesired` (~L422) | operator | agent | **none (deliberate)** | the mutation removes the transport | | `pbsdrheal` **Restage** path | system (reconciler) | agent | **none (deliberate)** | no generation bump → the agent's 60 s pbsdr ticker is the pickup path (a poke is a verified no-op); `internal/pbsdrheal/` byte-unchanged | | `api/handler.go` `reissueOnReenroll` (F2 claim + F3 offsite, ~L1071) | **system** (clean-slate re-enroll) | controller | **NEW intent bump** | — | | `web/configs.go` bumpIntent ×11 (L564/633/657/702/742/779/796/842/859/876/1055) | operator | controller | intent bump (pre-existing) | — | | `web/customer_reset.go` bumpIntent (~L226) | operator | controller | intent bump (pre-existing) | — | | `web/logtail.go` bumpIntent (~L35) / `web/logbundle.go` bumpIntent (~L68) | operator | controller | intent bump (pre-existing) | — | ### 9. Observations (not acted on) - **`handlePBSDRReissue` ↔ `ReissuePBSDR` duplication.** The operator button re-implements the reissue atom's tail (reissue → store secret → descriptor bump) instead of calling `ReissuePBSDR`, deliberately, to keep granular per-step HTTP error strings (the "keep in lockstep" comment marks the known duplication). Both now poke after their `SetHostDesired`. Not deduped (per spec forbidden-dedup); if ever unified, the handler's error-string granularity is the constraint to preserve. - **`pbsdrProvisionAtom` is poke-free by design** — its two callers poke on success (form path at L186, hook path at the new L312). This keeps the poke at the decision point (blocked vs minted) rather than inside the shared atom, matching the existing L186 structure.