Skip to content

refactor(edge,router): claim router work durably before provider invocation#51

Merged
ElbertePlinio merged 1 commit into
mainfrom
refactor/claim-first-router
Jul 19, 2026
Merged

refactor(edge,router): claim router work durably before provider invocation#51
ElbertePlinio merged 1 commit into
mainfrom
refactor/claim-first-router

Conversation

@ElbertePlinio

Copy link
Copy Markdown
Member

Summary

Issue #37, CAND-4 (PR 4 in the master plan): operator-router's idempotency only became durable after provider work ran. Two concurrent requests for the same idempotency key could both find nothing completed, both invoke the OpenAI-compatible provider, and the losing response was simply discarded after the winner's debit landed — wasted provider spend with no compensating record.

This durably claims a router attempt before any provider invocation, so at most one caller ever owns "go call the provider" for a given (user_id, idempotency_key) at a time.

What changed

  • New migration supabase/migrations/20260722000000_router_attempt_claim.sql: router_attempts table + router_attempt_claim / router_attempt_complete / router_attempt_fail RPCs (service_role only). A claim that's neither completed nor explicitly failed is recoverable once its lease elapses. Additive only — doesn't touch credit_ledger or debit_credits.
  • packages/edge-shared/src/index.ts: claimRouteAttempt, completeRouteAttempt, failRouteAttempt, findDebitedRouteResult own the RPC/route-metadata encode-decode boundary. createOperatorRouterHandler now claims → invokes provider → completes → debits, releasing the claim immediately on a definitive provider failure and skipping straight to a debit retry (no re-invocation) when a claim is found already completed. Legacy credit_ledger rows written before this migration remain authoritative for old completed routes, independent of whether a router_attempts row exists.
  • supabase/functions/operator-router/index.ts: wires the service-role client straight through; drops the adapter-owned findCompletedRoute query and debit callback.
  • Fast unit tests (packages/edge-shared/test/edge-shared.test.ts): FakeRouterServiceSupabase reimplements the claim/complete/fail/debit RPC contract, covering the full choreography, live claims (409 attempt_in_progress), lease recovery, and debit-retry-without-re-invoking-the-provider.
  • pgTAP (supabase/tests/database/router_attempt_claim.test.sql): claim/in_progress/completed outcomes, idempotent completion replay, failure recovery, lease-based recovery, per-user isolation, privilege grants, input validation.
  • Local-Postgres contract lane (packages/edge-shared/test/router-attempt.contract.test.ts, following test(billing): checkout lifecycle contract tests against local Postgres #47/refactor(sync): durable settings-sync LWW (#37 CAND-3) #50's convention): proves the durable functions against real Postgres, including two genuinely concurrent claims for the same key resolving to exactly one claim, and a full createOperatorRouterHandler run proving the provider is invoked and the ledger is debited exactly once across two concurrent same-key requests. Wired into bun run test:supabase.
  • Bumps @pickforge/* 0.11.0 → 0.12.0 and re-pins every Deno function's npm: import (existing publish-then-pin convention).

Validation

  • bun run typecheck
  • bun run test — 366/366 ✅
  • bun run build
  • supabase db reset — all migrations apply cleanly ✅
  • bun run test:supabase against real local Postgres:
    • pgTAP — 178 tests (including the new router_attempt_claim lane) ✅
    • welcome-credits-concurrency.ts
    • CAND-1 checkout-lifecycle.contract.test.ts — 20/20 (still green) ✅
    • CAND-3 sync LWW contract lane — 8/8 (still green) ✅
    • new router attempt contract lane — 8/8 ✅
  • supabase db advisors --local --type security --fail-on warn — clean ✅
  • deno check against the real npm registry isn't possible pre-publish (same documented gap as refactor(edge,billing): move fence/register/expire RPCs next to the deep lifecycle #48: published @pickforge/* is still behind main). Confirmed the pre-existing gap is unchanged — this diff's remaining deno check errors are a strict subset of main's — and validated the adapter's typing directly against local src via tsc instead.

Closes the "Router work is durably claimed before provider invocation" checklist item in #37.

…cation

Issue #37 (CAND-4): operator-router's idempotency only became durable
AFTER provider work. The handler checked `credit_ledger` for a completed
route, invoked the OpenAI-compatible provider, and only then wrote a
debit row keyed by the idempotency key — two concurrent requests for the
same key could both find nothing completed, both call the provider, and
the losing response was simply discarded after the winner's debit
landed (wasted provider spend with no compensating record).

New `router_attempts` table and three service-role-only RPCs
(`router_attempt_claim` / `router_attempt_complete` / `router_attempt_fail`,
supabase/migrations/20260722000000_router_attempt_claim.sql) move the
durable decision in front of the provider call: at most one caller ever
owns "go call the provider" for a given (user_id, idempotency_key) at a
time. A claim that is neither completed nor explicitly failed is
recoverable once its lease elapses. This is additive only: existing
`credit_ledger` rows written before this migration remain authoritative
for old completed routes (`findDebitedRouteResult`), independent of
whether a `router_attempts` row exists for that key.

`createOperatorRouterHandler` now claims before invoking the provider,
completes the claim with the provider outcome, then debits — releasing
the claim immediately on a definitive provider failure, and skipping
straight to a debit retry (no re-invocation) when a claim is found
already completed.

- `packages/edge-shared/src/index.ts`: `claimRouteAttempt`,
  `completeRouteAttempt`, `failRouteAttempt`, `findDebitedRouteResult`
  own the RPC/route-metadata encode-decode boundary; `debitCredits` is
  unchanged. `createOperatorRouterHandler` takes `serviceSupabase`
  instead of separate `findCompletedRoute`/`debit` callbacks, and
  returns 409 `attempt_in_progress` while another claim is live.
- `supabase/functions/operator-router/index.ts`: wires the caller's
  service-role client straight through; drops the adapter-owned
  `findCompletedRoute` query and `debit` callback.
- `packages/edge-shared/test/edge-shared.test.ts`: replaces the
  `findCompletedRoute`/`debit` fakes with a `FakeRouterServiceSupabase`
  reimplementing the claim/complete/fail/debit RPC contract, covering
  the full claim -> provider -> complete -> debit choreography, live
  claims, lease recovery, and debit-retry-without-re-invoking-the-
  provider.
- `supabase/tests/database/router_attempt_claim.test.sql`: pgTAP
  coverage for claim/in_progress/completed outcomes, idempotent
  completion replay, failure recovery, lease-based recovery, per-user
  isolation, privilege grants, and input validation.
- `packages/edge-shared/test/router-attempt.contract.test.ts` (+
  `router-fixtures.ts`, `postgres-router-client.ts`): a local-Postgres
  contract lane, following #47/#50's convention, proving the durable
  claim/complete/fail/debit functions against real Postgres, including
  two genuinely concurrent claims for the same key resolving to
  exactly one claim, and a full `createOperatorRouterHandler` run
  proving the provider is invoked and the ledger is debited exactly
  once across two concurrent same-key requests. Wired into
  `bun run test:supabase`.
- Bumps `@pickforge/*` 0.11.0 -> 0.12.0 and re-pins every Deno
  function's `npm:` import, per the existing publish-then-pin
  convention.

Validated: `bun run typecheck`; `bun run test` (366/366); `bun run
build`; `supabase db reset` (all migrations apply cleanly); `bun run
test:supabase` against real local Postgres — pgTAP (178 tests,
including the new router_attempt_claim lane), welcome-credits
concurrency, the CAND-1 checkout-lifecycle contract lane (20/20) and
CAND-3 sync LWW contract lane (8/8) stay green, and the new router
attempt contract lane (8/8) all pass; `supabase db advisors --local
--type security --fail-on warn` clean. `deno check` against the real
npm registry isn't possible pre-publish (same documented gap as #48:
published `@pickforge/*` is still behind main) — confirmed the
pre-existing gap is unchanged (this diff's remaining `deno check`
errors are a strict subset of main's) and validated the adapter's
typing directly against local `src` via `tsc` instead.
@ElbertePlinio
ElbertePlinio merged commit 30ae9c7 into main Jul 19, 2026
2 checks passed
@ElbertePlinio
ElbertePlinio deleted the refactor/claim-first-router branch July 19, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant