11 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.
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 onmain, local ==origin/mainre-confirmed before starting. - Hub v0.62.0 → v0.63.0. Deploy manifest
manifests/hub.yamlimage linefelhom-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 successfulSetHostDesired).hub/internal/api/handler.go— new nil-safePokerinterface +pokerfield +SetPoker; poke inhandleAdminSetDesiredState; intentBumpinreissueOnReenroll.hub/internal/api/wg.go—PokeAllHostsinhandleAdminSetOperatorPeer(only on a successfulBumpAllHostGenerations).hub/cmd/hub/main.go— capture the onepoke.Notifierand 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 (newpbsdr_poke_test.go). - Package
internal/api: +5 tests (newpoke_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 inwaiting_secret/consumed_failedafter 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↔ReissuePBSDRduplication. The operator button re-implements the reissue atom's tail (reissue → store secret → descriptor bump) instead of callingReissuePBSDR, deliberately, to keep granular per-step HTTP error strings (the "keep in lockstep" comment marks the known duplication). Both now poke after theirSetHostDesired. Not deduped (per spec forbidden-dedup); if ever unified, the handler's error-string granularity is the constraint to preserve.pbsdrProvisionAtomis 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.