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
2 changes: 0 additions & 2 deletions docs/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,6 @@ is **not** available on the Postgres backend yet — it degrades to no-context.
These are Cloudflare-platform features; they degrade cleanly and the core reviewer is unaffected:

- **Visual PR capture** (Browser Rendering binding) — off; reviews run text-only.
- **The `/mcp` server** (Durable-Object-backed Agents SDK) — returns `501`. The deterministic API + review
path is unaffected; a native MCP-on-Node port is a follow-up.
- **Distributed rate limiting** (RateLimiter Durable Object) — off by default; set `REDIS_URL` for a
Redis-backed fixed-window limiter (see §7). Otherwise put a reverse proxy / WAF in front.
- **Vectorize-backed RAG** and **R2 audit storage** — inert unless you wire equivalent backends.
11 changes: 9 additions & 2 deletions src/orb/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,20 @@ export async function storeRelayFailure(

/** Re-attempt pending relay failures. Called by the `retry-orb-relay` cron job every sweep cycle (≈2 min).
* Each row gets up to RELAY_RETRY_MAX_ATTEMPTS (5) retries within a 1-hour TTL; on success or expiry the row
* is removed. Never throws — a bad DB row or a persistently-down container is silently dropped after exhaustion. */
* is removed. Never throws — a bad DB row or a persistently-down container is dropped (with an alertable log,
* below) after exhaustion. */
export async function retryFailedRelays(env: Env, opts?: { fetchImpl?: typeof fetch }): Promise<void> {
// Prune rows whose TTL has elapsed or whose attempt budget is exhausted.
await env.DB
const pruned = await env.DB
.prepare("DELETE FROM orb_relay_failures WHERE expires_at < datetime('now') OR attempts >= ?")
.bind(RELAY_RETRY_MAX_ATTEMPTS)
.run();
// Make the drop VISIBLE (#5): a pruned row is a relay event we gave up delivering (1-hour TTL elapsed or 5
// retries exhausted) — e.g. a container down for over an hour. Emit an alertable structured log so the loss
// leaves a trace instead of vanishing silently.
if (pruned.meta.changes > 0) {
console.warn(JSON.stringify({ level: "warn", event: "orb_relay_events_dropped", count: pruned.meta.changes }));
}
const { results } = await env.DB
.prepare(
"SELECT delivery_id, event_name, installation_id, raw_body FROM orb_relay_failures WHERE expires_at >= datetime('now') AND attempts < ? ORDER BY created_at, delivery_id LIMIT ?",
Expand Down
6 changes: 5 additions & 1 deletion test/integration/orb-relay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,17 @@ describe("retryFailedRelays", () => {
expect(untouched?.n).toBe(5);
});

it("PRUNES rows that have exhausted their attempt budget (attempts >= 5)", async () => {
it("PRUNES rows that have exhausted their attempt budget (attempts >= 5) and logs the drop (#5)", async () => {
const e = brokeredEnv();
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
// Manually insert a row at the attempt ceiling.
await db(e).prepare("INSERT INTO orb_relay_failures (delivery_id, event_name, installation_id, raw_body, attempts) VALUES (?, ?, ?, ?, ?)").bind("exhausted-1", "pull_request", 9300, "{}", 5).run();
await retryFailedRelays(e);
const row = await db(e).prepare("SELECT delivery_id FROM orb_relay_failures WHERE delivery_id='exhausted-1'").first();
expect(row ?? null).toBeNull(); // pruned on the DELETE pass before the SELECT
// The drop is no longer silent — an alertable structured log records the lost event count.
expect(warn.mock.calls.some(([line]) => String(line).includes("orb_relay_events_dropped"))).toBe(true);
warn.mockRestore();
});

it("PRUNES expired rows (expires_at in the past) without attempting to forward", async () => {
Expand Down
Loading