Rescheduling is atomic
Why it matters
Non-atomic reschedules split a logically single operation into two sequential database calls: first release the old slot, then reserve the new one. If the new slot reservation fails (conflict, timeout, or error), the old slot has already been released — and the booking is now in an invalid state with no slot attached. CWE-362 (race condition) and CWE-841 both apply. The customer ends up with a booking record that points to no slot, or a booking that was moved to a slot that was simultaneously taken by another user. A transaction wrapping both mutations prevents this by rolling back the release if the reservation fails.
Severity rationale
Medium because non-atomic reschedules produce booking records in an invalid intermediate state on any failure during the two-step operation.
Remediation
Wrap the availability recheck, old-slot release, and new-slot reservation in a single transaction in src/lib/booking-service.ts.
await db.$transaction(async (tx) => {
const conflict = await tx.booking.findFirst({
where: { slotId: newSlotId, status: { not: 'CANCELLED' } }
});
if (conflict) throw new ConflictError('New slot unavailable');
await tx.booking.update({
where: { id: bookingId },
data: { slotId: newSlotId, startTime: newStartTime, status: 'CONFIRMED' }
});
// slot capacity adjustments go here too, inside the same transaction
});
Detection
-
ID:
rescheduling-logic -
Label: Rescheduling is atomic
-
Severity:
medium -
What to look for: Rescheduling should be treated as an atomic "Release Old Slot + Reserve New Slot" operation. Before evaluating, extract and quote the reschedule function or handler. Count all database mutations in the reschedule flow and verify they are wrapped in a single transaction. Enumerate: "X mutations in reschedule flow; Y of X are inside a transaction."
-
Pass criteria: The full reschedule operation (validate new availability, release old slot, book new slot) happens in a single transaction with at least 2 mutations. If the new slot reservation fails, the old slot must not be released. All mutations must be inside the same
$transaction,BEGIN...COMMIT, or equivalent block. Report:"Y of X reschedule mutations wrapped in transaction in [file:line]." -
Fail criteria: Old slot is released, then new slot is requested (risk of losing both if new fails). Or separate database calls without a wrapping transaction. Do NOT pass when the old booking is updated first and the new slot check happens in a separate non-transactional call.
-
Skip (N/A) when: Modifications/rescheduling are explicitly not allowed (no update endpoint exists).
-
Detail on fail:
"Rescheduling is not atomic. The old booking status is set to RESCHEDULED, then the new booking creation is attempted separately. If creation fails, the user has no booking." -
Cross-reference: The transaction-atomicity check in Conflict Prevention verifies general transaction usage; this check is specific to reschedule flows.
-
Cross-reference: The modification-validation check in this category verifies the same availability checks are applied during reschedule.
-
Cross-reference: The slot-availability-recheck check in Conflict Prevention verifies the new slot availability is verified inside the transaction.
-
Remediation: Wrap the reschedule operation in a single transaction in your booking service (e.g.,
src/lib/booking-service.ts).await db.$transaction(async (tx) => { // Check new slot available const conflict = await tx.booking.findFirst({ where: { slotId: newSlotId, status: { not: 'CANCELLED' } } }); if (conflict) throw new Error("New slot unavailable"); // Atomically update const oldBooking = await tx.booking.update({ where: { id: bookingId }, data: { status: 'RESCHEDULED', slotId: newSlotId, startTime: newStartTime } }); });
External references
- cwe · CWE-362 — Concurrent Execution Using Shared Resource with Improper Synchronization
- cwe · CWE-841 — Improper Enforcement of Behavioral Workflow
- iso-25010:2011 · functional-suitability.functional-correctness
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-flow-lifecycle·automated