Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/orb/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,16 @@ 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. 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")
.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, 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 }>();
if (!row) return "skipped"; // not a brokered self-host (or revoked) — nothing to relay to
Expand Down
31 changes: 31 additions & 0 deletions test/integration/orb-relay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,37 @@ 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<string, string>;
expect(h["x-orb-signature-256"]).toBe(`sha256=${await relaySignature(freshSecret, body)}`);
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<string, string>;
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);
Expand Down
Loading