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)