Skip to content

[CONSISTENCY] Concurrent first-card creation may allow multiple default cards per user #344

@udaycodespace

Description

@udaycodespace

Summary

Prevent possible race conditions during concurrent first-card creation where multiple cards may temporarily or permanently become isDefault: true for the same user.

Contexts

Current first-card initialization logic in apps/backend/src/routes/cards.ts relies on a standalone count() query before card creation:

const cardCount = await app.prisma.card.count({
  where: { userId }
});

isDefault: cardCount === 0

Under concurrent request conditions:

  • two create-card requests may execute simultaneously
  • both requests may observe cardCount === 0
  • both requests may create cards with isDefault: true

The current implementation already improves transactional consistency for:

  • card link replacement
  • default-card switching

However, first-card initialization still appears vulnerable to concurrent state inconsistencies depending on DB isolation behavior and request timing.

Tasks

  • Reproduce concurrent create-card race scenario
  • Investigate Prisma transaction/isolation behavior during simultaneous requests
  • Add concurrency-safe default-card assignment handling
  • Add regression tests covering concurrent card creation
  • Verify existing default-card flows remain unaffected

Acceptance Criteria

  • Only one default card can exist per user during concurrent creation requests
  • Concurrent create requests behave deterministically
  • Regression tests added for race-condition scenarios
  • Existing card CRUD flows continue functioning correctly

Proposed Approach

Initial investigation suggests the issue originates from relying on a non-atomic count() check before insert creation.

A possible direction is to move first-card initialization into a transaction-safe flow so concurrent requests cannot independently decide they should both become the default card.

Potential implementation strategies to investigate:

  • wrap default-card initialization inside a Prisma transaction with stronger isolation guarantees
  • replace the current count()-based approach with existence checks that execute atomically with creation logic
  • enforce a database-level consistency guarantee so only one isDefault: true card can exist per user
  • handle transactional conflicts/retries gracefully during simultaneous create requests

Possible DB-level approaches may include:

  • partial unique index/constraint scoped to (userId, isDefault=true)
  • transactional update/create sequencing
  • optimistic retry handling on unique constraint violations

The implementation should preserve existing behavior for:

  • manual default-card switching
  • card updates/deletion flows
  • card link replacement logic

Regression testing should simulate concurrent create-card requests to verify deterministic behavior under race conditions.

Area

backend

Difficulty

advanced

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions