feat(orb): pull-mode relay delivery for NAT/tailnet self-hosts - #1639
Conversation
A self-host engine behind NAT/Tailscale has no public inbound URL, so the Orb's push relay delivery is unreachable (observed: 257 dropped events, no fresh-PR reviews). The server side of pull mode already existed; this adds the engine side: - registerOrbRelayTarget honors ORB_RELAY_MODE=pull (registers no relay URL, no PUBLIC_API_ORIGIN required); push (default) unchanged. mode sent to register. - drainOrbRelay: POST /v1/orb/relay/pull with the secret + prior-batch ack ids, returns the validated event batch; best-effort ([] on non-broker/non-ok/throw). - enqueueWebhookByEnv: env-based core extracted from enqueueVerifiedWebhook (behavior identical) so the Context-less drain loop can enqueue. - server.ts: a 15s drain loop (when ORB_RELAY_MODE=pull) pulls events outbound + enqueues into the WEBHOOKS lane + acks handled ids. No inbound endpoint exposed. Closes #1638.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1639 +/- ##
=======================================
Coverage 95.53% 95.53%
=======================================
Files 204 204
Lines 22090 22113 +23
Branches 7975 7985 +10
=======================================
+ Hits 21103 21126 +23
Misses 412 412
Partials 575 575
🚀 New features to boost your workflow:
|
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ Gittensory review — safe to merge
✅ Approved — safe to merge Review summary
Nits — 6 non-blocking
Review context
Contributor next steps
Signal definitions
Review detailsGenerated from public PR metadata and the diff. Advisory only; deterministic signals remain authoritative. Clean, well-scoped implementation of pull-mode relay delivery. The `enqueueWebhookByEnv` extraction is byte-identical to the original path (same dedup guard, same error-flip-to-'error' on WEBHOOKS.send failure, same return shape), and the ack-on-success / no-ack-on-enqueue_failed logic in the drain loop is correct: only events durably handled are acked, so a real queue failure retries next tick while duplicate/invalid_json events are consumed rather than retried forever. The `registerOrbRelayTarget` change correctly gates the public-origin requirement to push mode only. No blockers. Nits (5)
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Summary
A self-host engine behind NAT/Tailscale (no public inbound URL) could not receive Orb events: the central Orb only supported push delivery (POST to the engine's registered relay URL), which is unreachable for a private engine. Diagnosed live: the engine's relay URL was a tailnet hostname with no Tailscale serve/funnel config, so 257 events failed to deliver to the active install — the engine got zero fresh PR events (reviews only ran via the periodic re-gate sweep, never within minutes of a PR opening).
The server side of pull mode already existed (
/v1/orb/relay/pull+enqueueRelayPending+forwardOrbEvent'srelay_mode === "pull"enqueue path). This PR adds the engine side, so a private engine drains events outbound with no inbound endpoint exposed:registerOrbRelayTarget(broker-client.ts): honorORB_RELAY_MODE—pullregisters with no relay URL (and no longer requiresPUBLIC_API_ORIGIN);push(default) is unchanged. The mode is sent to the register endpoint.drainOrbRelay(broker-client.ts): POST/v1/orb/relay/pullwith the enrollment secret + the previous batch'sackids (so the Orb deletes delivered events), returning the validated event batch. Best-effort (returns[]on any non-broker / unsafe-URL / non-ok / thrown case).enqueueWebhookByEnv(webhook.ts): the env-based core of the webhook enqueue (parse → dedup → record → WEBHOOKS lane), extracted fromenqueueVerifiedWebhook(whose behavior is byte-identical — it now just maps the result to a response). The drain loop has no Hono Context, so it needs the env-based form.server.ts): whenORB_RELAY_MODE=pull, a 15s timer drains → enqueues each event into the same WEBHOOKS lane the push receiver uses → acks the durably-handled ids next tick. Nothing is ever exposed publicly.Set
ORB_RELAY_MODE=pullin the self-host.envto use it. Closes #1638.Scope
broker-client.ts+webhook.ts+server.ts+ the broker-client test).site//CNAME/Pages; followsCONTRIBUTING.md.Validation
git diff --check·actionlint·typechecktest:coverage—registerOrbRelayTargetpull-mode (no URL, no public origin) + push-mode body now assertingmode;drainOrbRelaycovers parse + malformed-event filter + missing-events?? []+ non-ok + thrown + unsafe-URL →[].enqueueWebhookByEnvis covered by the existing webhook tests (the refactoredenqueueVerifiedWebhookreturns identical responses — all 4 arms exercised).server.ts(the drain loop) is codecov-ignored boot wiring.test:workers·build:mcp·test:mcp-pack·ui:*·npm audit --audit-level=moderateIf any required check was skipped, explain why:
ORB_RELAY_MODEis a self-hostprocess.envvar (not a wrangler binding); the pull server-side + schema already exist.Safety
[](no throw) on non-broker / non-ok / thrown / unsafe-URL; enqueue dedups + records-error on failure (unchanged).Notes
broker-client.ts/webhook.tsrun on the self-host engine; ships on the next engine rebuild +ORB_RELAY_MODE=pull.