From 495eb9878708de53c4f42f45119e006ee0626511 Mon Sep 17 00:00:00 2001 From: Ramadhan Sjamsani Date: Mon, 1 Jun 2026 22:27:07 +0800 Subject: [PATCH] fix(db): widen customer_transactions.type to VARCHAR(128) TransactionType.FIRST_SESSION_DISCOUNT ('first_session_discount', 22 chars) overflowed the VARCHAR(20) column, throwing in acceptPairingRequest AFTER the session was flipped to ACTIVE but before startSessionTimer/startSessionListener/PAIRED-notify ran. Every first-session-discount pairing thus half-completed: lost transaction row, no server-side timer, and a 500 to the mitra so its app never opened the chat. Widen the column (CREATE TABLE + idempotent ALTER). Deferred hardening (bookkeeping INSERT in the critical path) logged in TECH_DEBT. Co-Authored-By: Claude Opus 4.8 (1M context) --- TECH_DEBT.md | 24 ++++++++++++++++++++++++ backend/src/db/migrate.js | 9 ++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/TECH_DEBT.md b/TECH_DEBT.md index 8aa2d16..871eb97 100644 --- a/TECH_DEBT.md +++ b/TECH_DEBT.md @@ -10,6 +10,30 @@ to act on it without re-deriving the discussion. ## Backend +### `[2026-06-01]` Bookkeeping INSERT sits in the pairing critical path + +**File:** `backend/src/services/pairing.service.js` (`acceptPairingRequest`, ~line 506) + +**What happened:** the `INSERT INTO customer_transactions` runs *after* the session +is flipped to `ACTIVE` but *before* `startSessionTimer`, `startSessionListener`, +the customer `PAIRED` WS notify, and the other-mitra dismiss fan-out. A +`varchar(20)` overflow on `type = 'first_session_discount'` (22 chars) threw +there, so every first-session-discount pairing half-completed: no transaction +row, no server-side timer, no PAIRED push (customer recovered via polling), and a +500 returned to the mitra so its app never opened the chat. + +**Fixed now:** column widened to `VARCHAR(128)` (migrate.js), so the INSERT no +longer throws. + +**Why it's still debt:** a *bookkeeping* write can still abort *critical* pairing +steps if it ever fails again (constraint change, DB hiccup, future longer enum). +Hardening: either move the `customer_transactions` INSERT to the end of +`acceptPairingRequest`, or wrap it in a `try/catch` that logs-but-doesn't-throw, +so transaction recording can never again half-complete a pairing. Same applies to +the equivalent INSERT in `extension.service.js`. + +--- + ### `[2026-05-11]` Public `GET /api/public/bestie/available` needs rate limiting before prod **File:** `backend/src/routes/public/public.bestie-availability.routes.js` diff --git a/backend/src/db/migrate.js b/backend/src/db/migrate.js index a55d2ed..68eb036 100644 --- a/backend/src/db/migrate.js +++ b/backend/src/db/migrate.js @@ -226,12 +226,19 @@ const migrate = async () => { id UUID PRIMARY KEY DEFAULT gen_random_uuid(), customer_id UUID NOT NULL REFERENCES customers(id), session_id UUID NOT NULL REFERENCES chat_sessions(id), - type VARCHAR(20) NOT NULL, + type VARCHAR(128) NOT NULL, amount INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) ` + // Idempotent widen for DBs created when this column was VARCHAR(20): the + // TransactionType.FIRST_SESSION_DISCOUNT value 'first_session_discount' is + // 22 chars and overflowed varchar(20), throwing in acceptPairingRequest() + // *after* the session was already marked ACTIVE — losing the transaction row, + // the server-side timer, the PAIRED WS notify, and returning 500 to the mitra. + await sql`ALTER TABLE customer_transactions ALTER COLUMN type TYPE VARCHAR(128)` + await sql` CREATE INDEX IF NOT EXISTS idx_customer_transactions_customer_id ON customer_transactions (customer_id)