Canonical spec for the two receiver endpoints every Resonance spoke MUST expose so the hub can push updates and validate health. Combined with the existing /api/public/rop/* pull endpoints this gives full push + pull fallback authority: config, tier gates, kill-switches, and ROP optimizations auto-apply without manual per-spoke work.
Both endpoints live under /api/public/* (auth-bypassed) and are secured with the same per-app HMAC scheme used by ROP. Reject any request that fails verification.
/api/public/hub-control/pull-config from the hub.feature_flags, tunables, tier_catalog, and suggestions to local runtime state (in-memory + cache).200 { ok: true, applied_at }.Payload (JSON): { kind: "push_nudge" | "apply_config", hub_app_id, reason? }.
Health probe. Return 200 { ok: true, version, checks: { db, config_age_s } } within 5 s. Any non-200 is treated as unhealthy on /admin/spoke-health.
Headers on every hub → spoke request:
x-hub-app — the spoke's hub_apps.id (uuid).x-hub-timestamp — unix seconds. Reject if |now - ts| > 300.x-hub-signature — hex(hmac_sha256(secret, "${ts}.${rawBody}")).The secret is the value the hub owner shared with you at onboarding (stored as HUB_SIGNING_SECRET in the spoke). It equals hub_apps.signing_key_hash in the hub DB.
Paste-ready TypeScript reference lives in the repo at docs/spoke-hub-control-contract.md.
Every spoke should also poll /api/public/hub-control/pull-config every 60 s in case a push is missed.