From 2d72feb0c8889e34634777f2a30ecf55f1816746 Mon Sep 17 00:00:00 2001 From: galuis116 Date: Tue, 30 Jun 2026 02:57:52 +0400 Subject: [PATCH 1/3] fix(orb): prefer the newest relay enrollment when forwarding events (#1783) --- src/orb/relay.ts | 8 +++++++- test/integration/orb-relay.test.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/orb/relay.ts b/src/orb/relay.ts index e4c9236f22..3ddf5b56bf 100644 --- a/src/orb/relay.ts +++ b/src/orb/relay.ts @@ -291,8 +291,14 @@ export async function forwardOrbEvent( fetchImpl: typeof fetch = fetch, ): Promise<"forwarded" | "queued" | "skipped" | "failed"> { if (!args.installationId || !RELAY_FORWARD_EVENTS.has(args.eventName)) return "skipped"; + // issueOrbEnrollment INSERTs a new row per enrollment without revoking prior enrolled rows for the same + // installation_id. Without ORDER BY, .first() is nondeterministic — a stale row (no relay / old URL) can win + // after re-enrollment (#1783). Prefer enrollments with a registered relay (SQLite sorts NULL first on DESC), + // then the newest relay registration, then the newest enrollment. const row = await env.DB - .prepare("SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL") + .prepare( + "SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL ORDER BY (relay_registered_at IS NOT NULL) DESC, relay_registered_at DESC, enrolled_at DESC", + ) .bind(args.installationId) .first<{ relay_mode: string; relay_url: string | null; relay_secret_enc: string | null; relay_secret_iv: string | null; relay_secret_salt: string | null }>(); if (!row) return "skipped"; // not a brokered self-host (or revoked) — nothing to relay to diff --git a/test/integration/orb-relay.test.ts b/test/integration/orb-relay.test.ts index 65b4fa6376..6ad21c913b 100644 --- a/test/integration/orb-relay.test.ts +++ b/test/integration/orb-relay.test.ts @@ -181,6 +181,21 @@ describe("forwardOrbEvent", () => { expect(calls[0]?.init?.body).toBe(body); }); + it("FORWARDS via the newest enrollment when multiple enrolled rows exist for one installation (regression for #1783)", async () => { + const e = brokeredEnv(); + await seedInstall(e, 804); + const staleSecret = ((await issueOrbEnrollment(e, 804)) as { secret: string }).secret; // row A — enrolled, no relay + const freshSecret = ((await issueOrbEnrollment(e, 804)) as { secret: string }).secret; // row B — stays enrolled too + await registerOrbRelay(e, freshSecret, "https://new-host.example/v1/orb/relay"); + const { fetchImpl, calls } = capture(new Response("ok")); + const body = '{"action":"opened","number":9}'; + expect(await forwardOrbEvent(e, { eventName: "pull_request", installationId: 804, deliveryId: "del-1783", rawBody: body }, fetchImpl)).toBe("forwarded"); + expect(calls[0]?.url).toBe("https://new-host.example/v1/orb/relay"); + const h = calls[0]?.init?.headers as Record; + expect(h["x-orb-signature-256"]).toBe(`sha256=${await relaySignature(freshSecret, body)}`); + expect(staleSecret).not.toBe(freshSecret); + }); + it("returns FAILED (never throws) on a non-ok response or a thrown fetch — the Orb 202 always stands", async () => { const e = brokeredEnv(); const secret = await enroll(e, 802); From 14ca8bc59bf3a087dab3dcd1da3f7558d38e9273 Mon Sep 17 00:00:00 2001 From: galuis116 Date: Tue, 30 Jun 2026 03:07:10 +0400 Subject: [PATCH 2/3] fix(orb): add created_at/enroll_id tie-breakers for relay enrollment lookup (#1783) --- src/orb/relay.ts | 5 +++-- test/integration/orb-relay.test.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/orb/relay.ts b/src/orb/relay.ts index 3ddf5b56bf..5086ddbf0d 100644 --- a/src/orb/relay.ts +++ b/src/orb/relay.ts @@ -294,10 +294,11 @@ export async function forwardOrbEvent( // issueOrbEnrollment INSERTs a new row per enrollment without revoking prior enrolled rows for the same // installation_id. Without ORDER BY, .first() is nondeterministic — a stale row (no relay / old URL) can win // after re-enrollment (#1783). Prefer enrollments with a registered relay (SQLite sorts NULL first on DESC), - // then the newest relay registration, then the newest enrollment. + // then the newest relay registration, then the newest enrollment, with created_at/enroll_id tie-breakers when + // SQLite CURRENT_TIMESTAMP ties at second resolution (#1783). const row = await env.DB .prepare( - "SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL ORDER BY (relay_registered_at IS NOT NULL) DESC, relay_registered_at DESC, enrolled_at DESC", + "SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL ORDER BY (relay_registered_at IS NOT NULL) DESC, relay_registered_at DESC, enrolled_at DESC, created_at DESC, enroll_id DESC", ) .bind(args.installationId) .first<{ relay_mode: string; relay_url: string | null; relay_secret_enc: string | null; relay_secret_iv: string | null; relay_secret_salt: string | null }>(); diff --git a/test/integration/orb-relay.test.ts b/test/integration/orb-relay.test.ts index 6ad21c913b..e681d3ff39 100644 --- a/test/integration/orb-relay.test.ts +++ b/test/integration/orb-relay.test.ts @@ -196,6 +196,22 @@ describe("forwardOrbEvent", () => { expect(staleSecret).not.toBe(freshSecret); }); + it("prefers the newest registered relay when two enrolled rows tie on relay_registered_at (regression for #1783 tie-break)", async () => { + const e = brokeredEnv(); + await seedInstall(e, 805); + const staleSecret = ((await issueOrbEnrollment(e, 805)) as { secret: string }).secret; + await registerOrbRelay(e, staleSecret, "https://stale-host.example/v1/orb/relay"); + const freshSecret = ((await issueOrbEnrollment(e, 805)) as { secret: string }).secret; + await registerOrbRelay(e, freshSecret, "https://new-host.example/v1/orb/relay"); + await db(e).prepare("UPDATE orb_enrollments SET relay_registered_at = '2026-06-30T00:00:00Z' WHERE installation_id = 805").run(); + const { fetchImpl, calls } = capture(new Response("ok")); + const body = '{"action":"opened","number":10}'; + expect(await forwardOrbEvent(e, { eventName: "pull_request", installationId: 805, deliveryId: "del-tie", rawBody: body }, fetchImpl)).toBe("forwarded"); + expect(calls[0]?.url).toBe("https://new-host.example/v1/orb/relay"); + const h = calls[0]?.init?.headers as Record; + expect(h["x-orb-signature-256"]).toBe(`sha256=${await relaySignature(freshSecret, body)}`); + }); + it("returns FAILED (never throws) on a non-ok response or a thrown fetch — the Orb 202 always stands", async () => { const e = brokeredEnv(); const secret = await enroll(e, 802); From 92d9f7b9afecf2a7a7fba2e573420c177496b5ef Mon Sep 17 00:00:00 2001 From: galuis116 Date: Tue, 30 Jun 2026 03:27:30 +0400 Subject: [PATCH 3/3] fix(orb): break relay enrollment ties on rowid, not random enroll_id (#1783) enroll_id is a random opaque token and CURRENT_TIMESTAMP ties at second resolution, so the prior created_at/enroll_id tie-breakers were non-deterministic when multiple enrolled rows shared a relay_registered_at. Order by the implicit rowid (monotonic insertion order) so the most-recently-inserted enrollment wins deterministically. --- src/orb/relay.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/orb/relay.ts b/src/orb/relay.ts index 5086ddbf0d..6dd70c2c98 100644 --- a/src/orb/relay.ts +++ b/src/orb/relay.ts @@ -294,11 +294,12 @@ export async function forwardOrbEvent( // issueOrbEnrollment INSERTs a new row per enrollment without revoking prior enrolled rows for the same // installation_id. Without ORDER BY, .first() is nondeterministic — a stale row (no relay / old URL) can win // after re-enrollment (#1783). Prefer enrollments with a registered relay (SQLite sorts NULL first on DESC), - // then the newest relay registration, then the newest enrollment, with created_at/enroll_id tie-breakers when - // SQLite CURRENT_TIMESTAMP ties at second resolution (#1783). + // then the newest relay registration, then the newest enrollment. The final tie-break is the implicit rowid + // (monotonic insertion order) — enroll_id is a random opaque token, and CURRENT_TIMESTAMP ties at second + // resolution, so rowid is the only stable "most recently inserted" key when those collide (#1783). const row = await env.DB .prepare( - "SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL ORDER BY (relay_registered_at IS NOT NULL) DESC, relay_registered_at DESC, enrolled_at DESC, created_at DESC, enroll_id DESC", + "SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL ORDER BY (relay_registered_at IS NOT NULL) DESC, relay_registered_at DESC, enrolled_at DESC, rowid DESC", ) .bind(args.installationId) .first<{ relay_mode: string; relay_url: string | null; relay_secret_enc: string | null; relay_secret_iv: string | null; relay_secret_salt: string | null }>();