# Transaction atomicity enforced

- **Pattern:** `ab-000546` (`booking-flow-lifecycle.double-booking.transaction-atomicity`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000546
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000546)

## Why it matters

Booking creation typically writes to multiple tables: the booking record, the slot's capacity counter, a payment reference, and possibly an audit log. Sequential `await db.*.create/update` calls outside a transaction are not atomic — if any step fails, all previous writes remain committed. CWE-362 and CWE-841 both apply: partial writes leave orphaned booking records with no slot decrement, or a slot marked unavailable with no corresponding booking. These orphaned records accumulate silently and are only discovered when a customer reports a missing booking or a slot appears permanently blocked.

## Severity rationale

Critical because non-atomic multi-table writes produce orphaned records on partial failure, causing silent data corruption that is difficult to detect and expensive to remediate.

## Remediation

Wrap every multi-table mutation in a single transaction in your booking service (e.g., `src/lib/booking-service.ts`). All writes succeed together or none does.

```typescript
await db.$transaction([
  db.booking.create({ data: { ...bookingData, status: 'CONFIRMED' } }),
  db.slot.update({
    where: { id: slotId },
    data: { bookedCount: { increment: 1 } }
  }),
]);
```

## Detection

- **ID:** `transaction-atomicity`
- **Label:** Transaction atomicity enforced
- **Severity:** `critical`
- **What to look for:** Count all database tables modified during a single booking creation flow (booking record, slot status, payment record, audit log, notification log, etc.). Then verify that all these mutations happen in a single transaction block. Look for `db.$transaction`, `sequelize.transaction()`, `knex.transaction()`, `BEGIN...COMMIT` in raw SQL, or equivalent. Count: "X tables modified in booking creation; Y of X are inside a transaction."
- **Pass criteria:** All related database mutations (at least 2 tables) are wrapped in a single transaction (Prisma `$transaction`, Sequelize `transaction()`, SQL `BEGIN...COMMIT`). If any step fails, all preceding steps are rolled back. Report: `"X of Y table mutations wrapped in transaction in [file:line]."`
- **Fail criteria:** Mutations happen in separate calls outside a transaction. If step 2 fails, step 1 remains (data inconsistency, orphaned records). Do NOT pass when multiple `await db.*.create/update` calls appear sequentially without a wrapping transaction — even if they "usually work," they are not atomic.
- **Skip (N/A) when:** Only a single database table is modified during booking creation (no slot status update, no related records).
- **Detail on fail:** `"Booking creation and Slot status update happen in separate Prisma calls. If Slot update fails, an orphaned PENDING booking remains."`
- **Cross-reference:** The concurrent-request-handling check in this category verifies the UNIQUE constraint that prevents duplicates within this transaction.
- **Cross-reference:** The cancellation-state-machine check in Lifecycle Management verifies cancellation uses similar transactional writes.
- **Cross-reference:** The rescheduling-logic check in Lifecycle Management verifies atomic slot swaps.
- **Remediation:** Wrap all related mutations in a single transaction in your booking service (e.g., `src/lib/booking-service.ts`).

  ```typescript
  await db.$transaction([
    db.booking.create({ data: {...} }),
    db.slot.update({ where: { id: slotId }, data: { available: false } }),
  ]);
  ```

## External references

- cwe CWE-362
- cwe CWE-841
- owasp A04
- iso-25010 functional-suitability.functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000546
