You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The self-host relay pipeline (orb_relay_pending table, added in migrations/0079_orb_relay_pull.sql)
is currently GitHub-webhook-only: every row has a free-text event_name and a raw_body that the
pull-drain loop (src/selfhost/monitored-work.ts's drainOrbRelayWithMonitor) always hands to enqueueWebhookByEnv (src/github/webhook.ts), which unconditionally does JSON.parse(rawBody) as GitHubWebhookPayload.
Per #4902's own design comment (2026-07-18), this pipeline is the correct place to add an
Orb-operational config-push channel — it already has real per-installation auth (a hashed
enrollment secret bound to an installation_id, orb_enrollments.secret_hash), and self-host
containers in pull mode are already polling it. This issue adds the write side: a new discriminator
column and an internal, operator-gated endpoint that enqueues a typed payload. It does not
change dispatch behavior for any existing row — see the companion issue for the read/dispatch side.
Scope boundary, stated explicitly per this repo's own "gate only enforces what's written" rule:
this issue is restricted to Orb's own operational state (enrollment lifecycle notices, capability
announcements, deprecation notices) — it must NOT auto-apply anything that overrides an operator's
own .loopover.yml/DB settings. It targets an explicit list of installation_ids only — no
percentage/canary rollout selector (there is no existing rollout-percentage primitive in this
codebase to build one on top of; inventing one here is out of scope).
Requirements
Add migration migrations/0168_orb_relay_pending_kind.sql: ALTER TABLE orb_relay_pending ADD COLUMN kind TEXT NOT NULL DEFAULT 'github_webhook'; — the
default preserves identical behavior for every existing and future GitHub-webhook row.
Add a new internal endpoint (e.g. POST /v1/internal/fleet/config-push) gated via requireAppRole(c, ["operator"]), mirroring the existing kill-switch endpoint's auth shape
(src/api/routes.ts, POST /v1/app/kill-switch, ~L1874-1897) and its zod-validated-body pattern
(killSwitchUpdateSchema).
The endpoint accepts an explicit array of target installation_ids and inserts one orb_relay_pending row per target with kind = 'config_push' and the typed payload as raw_body (JSON-stringified).
Do not modify enqueueWebhookByEnv, drainOrbRelayWithMonitor, or any other read/dispatch code
in this issue — this is the write side only.
Deliverables
Migration adding the kind column (default 'github_webhook')
Typed config_push payload schema (zod), validated the same way killSwitchUpdateSchema is
New operator-gated endpoint inserting one orb_relay_pending row per targeted installation_id
Unit/integration tests: successful multi-target enqueue; non-operator caller rejected (403);
malformed payload rejected (400); a pre-existing row (no kind set explicitly) still defaults
to 'github_webhook' and is unaffected
Test Coverage Requirements
This touches src/api/routes.ts, src/db/* (or wherever the migration runner lives), and a new
schema module — all under src/**, so the repo's 99%+ Codecov patch gate (branch-counted) applies
to every changed line/branch. Add tests under test/integration/api.test.ts (endpoint
auth/validation/insert paths) and a dedicated unit test file for the payload schema.
Expected Outcome
An operator can push a typed, addressed config/capability-notice message to an explicit list of
installations through a new internal endpoint. The message lands in the existing orb_relay_pending queue, indistinguishable in storage from a GitHub-webhook row except for its new kind column — no existing webhook delivery behavior changes.
migrations/0079_orb_relay_pull.sql — the table this extends
src/api/routes.ts (~L1863-1897) — the kill-switch endpoint's auth/validation pattern to mirror
src/orb/relay.ts, src/orb/broker-client.ts's drainOrbRelay — existing relay read-side, for
the response shape (deliveryId/eventName/rawBody) this write side must stay compatible with
Companion issue (dispatch side) — the read side this write path feeds
Context
The self-host relay pipeline (
orb_relay_pendingtable, added inmigrations/0079_orb_relay_pull.sql)is currently GitHub-webhook-only: every row has a free-text
event_nameand araw_bodythat thepull-drain loop (
src/selfhost/monitored-work.ts'sdrainOrbRelayWithMonitor) always hands toenqueueWebhookByEnv(src/github/webhook.ts), which unconditionally doesJSON.parse(rawBody) as GitHubWebhookPayload.Per #4902's own design comment (2026-07-18), this pipeline is the correct place to add an
Orb-operational config-push channel — it already has real per-installation auth (a hashed
enrollment secret bound to an
installation_id,orb_enrollments.secret_hash), and self-hostcontainers in pull mode are already polling it. This issue adds the write side: a new discriminator
column and an internal, operator-gated endpoint that enqueues a typed payload. It does not
change dispatch behavior for any existing row — see the companion issue for the read/dispatch side.
Scope boundary, stated explicitly per this repo's own "gate only enforces what's written" rule:
this issue is restricted to Orb's own operational state (enrollment lifecycle notices, capability
announcements, deprecation notices) — it must NOT auto-apply anything that overrides an operator's
own
.loopover.yml/DB settings. It targets an explicit list ofinstallation_ids only — nopercentage/canary rollout selector (there is no existing rollout-percentage primitive in this
codebase to build one on top of; inventing one here is out of scope).
Requirements
migrations/0168_orb_relay_pending_kind.sql:ALTER TABLE orb_relay_pending ADD COLUMN kind TEXT NOT NULL DEFAULT 'github_webhook';— thedefault preserves identical behavior for every existing and future GitHub-webhook row.
config_pushpayload shape:{ pushId: string, message: string, capability?: string, deprecatesAt?: string }.POST /v1/internal/fleet/config-push) gated viarequireAppRole(c, ["operator"]), mirroring the existing kill-switch endpoint's auth shape(
src/api/routes.ts,POST /v1/app/kill-switch, ~L1874-1897) and its zod-validated-body pattern(
killSwitchUpdateSchema).installation_ids and inserts oneorb_relay_pendingrow per target withkind = 'config_push'and the typed payload asraw_body(JSON-stringified).enqueueWebhookByEnv,drainOrbRelayWithMonitor, or any other read/dispatch codein this issue — this is the write side only.
Deliverables
kindcolumn (default'github_webhook')config_pushpayload schema (zod), validated the same waykillSwitchUpdateSchemaisorb_relay_pendingrow per targetedinstallation_idmalformed payload rejected (400); a pre-existing row (no
kindset explicitly) still defaultsto
'github_webhook'and is unaffectedTest Coverage Requirements
This touches
src/api/routes.ts,src/db/*(or wherever the migration runner lives), and a newschema module — all under
src/**, so the repo's 99%+ Codecov patch gate (branch-counted) appliesto every changed line/branch. Add tests under
test/integration/api.test.ts(endpointauth/validation/insert paths) and a dedicated unit test file for the payload schema.
Expected Outcome
An operator can push a typed, addressed config/capability-notice message to an explicit list of
installations through a new internal endpoint. The message lands in the existing
orb_relay_pendingqueue, indistinguishable in storage from a GitHub-webhook row except for its newkindcolumn — no existing webhook delivery behavior changes.Links & Resources
3-piece breakdown
migrations/0079_orb_relay_pull.sql— the table this extendssrc/api/routes.ts(~L1863-1897) — the kill-switch endpoint's auth/validation pattern to mirrorsrc/orb/relay.ts,src/orb/broker-client.ts'sdrainOrbRelay— existing relay read-side, forthe response shape (
deliveryId/eventName/rawBody) this write side must stay compatible with