Found 2026-09-04 by the finance-train verification on the Netlify deploy preview of #1422 and confirmed pre-existing on dev (and therefore on production, which runs the same PG_POOL_MAX=1).
Symptom. POST /api/checkout for a plain Razorpay consultation or subscription answers HTTP 500 timeout exceeded when trying to connect every time, while sibling database routes on the same deploy answer in 1 to 5 seconds. No partial rows are written.
Cause. validateSlotAvailability (lib/payments/operations/checkout.ts ~L857) calls checkConsent, and checkConsent reads on the GLOBAL Prisma client (lib/compliance/dpdp.ts:194). validateSlotAvailability only ever runs inside a transaction (calculateAmountAndValidate at ~L500, revalidateInsideLock at ~L1414, handleConsultationCheckout at ~L2861). With a pool of one connection (lib/prisma.ts:52,65) the open transaction holds the only client, the nested read waits for a connection only that transaction can release, and pg gives up at the 3 s connect timeout. A second instance sits at ~L1528 (org-sponsored consent inside revalidateInsideLock). Both arrived with commit 20fef46 (Wave 7 consent cascade, #1255). The train's local E2E passed only because a dev server runs poolMax=10.
Fix. PR #1435: checkConsent accepts an optional client defaulting to the global one, and both in-transaction sites pass tx. The gate still fails closed; the pool is not widened. A jest pin models the single-connection pool: the global client throws the production error text whenever it is touched while $transaction is open (red on the old code, green after).
Also on the checkout path (not fatal, same family). Three fire-and-forget global-client lookups run from inside the Serializable transaction and lose their Novu notification silently under poolMax=1: checkout.ts ~L3190 (program cap hit), ~L3287 (80% cap bell), lib/payments/billing/overage-settlement.ts ~L245 (overage due). They should move to the post-commit pattern invoice-rollup.ts uses.
Owner actions. Merge #1435 ahead of the train and cut a dev → prod release; production has not been able to take a consultation payment since #1255 merged.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
Found 2026-09-04 by the finance-train verification on the Netlify deploy preview of #1422 and confirmed pre-existing on dev (and therefore on production, which runs the same PG_POOL_MAX=1).
Symptom. POST /api/checkout for a plain Razorpay consultation or subscription answers HTTP 500
timeout exceeded when trying to connectevery time, while sibling database routes on the same deploy answer in 1 to 5 seconds. No partial rows are written.Cause.
validateSlotAvailability(lib/payments/operations/checkout.ts ~L857) callscheckConsent, andcheckConsentreads on the GLOBAL Prisma client (lib/compliance/dpdp.ts:194).validateSlotAvailabilityonly ever runs inside a transaction (calculateAmountAndValidateat ~L500,revalidateInsideLockat ~L1414,handleConsultationCheckoutat ~L2861). With a pool of one connection (lib/prisma.ts:52,65) the open transaction holds the only client, the nested read waits for a connection only that transaction can release, and pg gives up at the 3 s connect timeout. A second instance sits at ~L1528 (org-sponsored consent insiderevalidateInsideLock). Both arrived with commit 20fef46 (Wave 7 consent cascade, #1255). The train's local E2E passed only because a dev server runs poolMax=10.Fix. PR #1435:
checkConsentaccepts an optional client defaulting to the global one, and both in-transaction sites passtx. The gate still fails closed; the pool is not widened. A jest pin models the single-connection pool: the global client throws the production error text whenever it is touched while$transactionis open (red on the old code, green after).Also on the checkout path (not fatal, same family). Three fire-and-forget global-client lookups run from inside the Serializable transaction and lose their Novu notification silently under poolMax=1: checkout.ts ~L3190 (program cap hit), ~L3287 (80% cap bell), lib/payments/billing/overage-settlement.ts ~L245 (overage due). They should move to the post-commit pattern invoice-rollup.ts uses.
Owner actions. Merge #1435 ahead of the train and cut a dev → prod release; production has not been able to take a consultation payment since #1255 merged.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7