Repository navigation
feat(studio): meter prompts on the server and count limits in postgres - #27
Merged
Merged
Conversation
Every POST /api/studio/turn now spends a prompt before the proxy forwards it. Guests get 5 a day per network address, an IPv6 address counted by its /64, so a cleared or private browser starts no fresh allowance. Signed-in users get 75 a day by user id. Both reset at 00:00 UTC, and a turn the harness does not start gets its prompt back (src/lib/studio/spendPrompt.ts). The proxy forwards only turn, interrupt, cancel and composition by exact name, so no encoded path reaches the turn route unmetered. The per-IP limiter for /api and /mcp moves from Redis, which production never had, to the limit_counter table (migration 0017). It counts on a two-connection pool that fails open within 2 s. ioredis and REDIS_URL are removed.
pg-pool emits 'error' on the pool when an idle client's backend goes away, as on a Cloud SQL restart or failover. With no listener Node treats it as an uncaught error and the instance exits. buildRuntimePool now attaches a listener that logs, so both pools in src/lib/db/index.ts carry one. db/test/pool-idle-error.test.ts terminates an idle backend and checks the pool keeps serving.
jm-rivera
added this pull request to stack #39
October 20, 2026 13:42
jm-rivera
marked this pull request as ready for review
October 20, 2026 15:23
Reserve allowance only for admitted turns and refund the original UTC window. Run reservations, refunds, and pruning on the bounded counter pool so stalled counters leave account and history connections free. Add regression coverage for concurrent reservations, refused attempts, window rollover, and database connection and statement timeouts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Studio forwarded every model turn while the guest limit lived in localStorage. Each
POST /api/studio/turnatomically reserves one prompt from the caller's allowance in Postgres before forwarding. Guests get 5 prompts a day per network address, with IPv6 addresses grouped by /64, and signed-in users get 75 a day by user id. Both reset at 00:00 UTC. Guests behind a shared address share its allowance. Exhausted allowances return 429 withRetry-After. Database failures or the five-second admission deadline return 503. Refused requests consume no allowance. A failed turn refunds the exact window it reserved, so an old-day failure leaves the new day's usage intact.The proxy forwards only
turn,interrupt,cancel, andcomposition, matched as whole route names, so encoded paths cannot reach the harness's turn route without metering.refreshstays server-side with its own principal.The per-IP limits on
/apiand/mcpmove from Redis to thelimit_countertable in migration 0017. All counter work uses a separate two-connection pool with a 2-second connection timeout, a 500ms client query timeout, and a 400ms Postgres statement timeout, including prompt reservations, refunds, and pruning. Per-IP limiter failures let requests through. Studio allowance failures refuse the turn. Counter stalls leave account and history connections available.ioredisandREDIS_URLare removed, and runtime pools log idle connection errors.The generic
countHitandrefundHitAPIs used by the stacked email-code sign-in PR #28 retain their signatures and behaviour.Testing:
pnpm typecheck,pnpm lint,pnpm build, andpnpm testpass, with 1,659 tests on Postgres 18.4. Regression tests cover concurrent reservations at the allowance, refused requests followed by refunds, refunds across midnight, Postgres row-lock cancellation, and pool exhaustion.