# Modified booking sends updated .ics with the same UID and incremented SEQUENCE

- **Pattern:** `ab-000569` (`booking-notifications-reminders.calendar-invites.ics-modified-sequence`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000569
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000569)

## Why it matters

RFC 5545 defines SEQUENCE as the mechanism calendar clients use to determine whether an incoming invite supersedes an existing one. When a modified booking is sent without an incremented SEQUENCE, clients that have already processed a SEQUENCE:0 invite ignore or flag the incoming update as stale — the customer's calendar retains the old time and location. The result is a no-show caused not by the customer but by the booking system's failure to follow the standard. Without a `sequence` column in the database, the increment cannot be persisted across calls, meaning the SEQUENCE will reset to 0 on each modification and every update will be treated as stale.

## Severity rationale

High because an absent or non-incrementing SEQUENCE causes calendar clients to silently discard reschedule notifications, producing no-shows caused by the system rather than the customer.

## Remediation

Add a `sequence` integer column to the bookings table, increment it on every modification, and pass the stored value to the ICS generator in `src/lib/bookings.ts`:

```ts
async function modifyBooking(bookingId: string, changes: Partial<Booking>) {
  const booking = await db.bookings.findUnique({ where: { id: bookingId } })
  const updated = await db.bookings.update({
    where: { id: bookingId },
    data: { ...changes, sequence: (booking.sequence ?? 0) + 1 },
  })
  const icsData = await generateBookingICS(updated, booking.host)
  await emailQueue.add('send-modification-email', {
    bookingId,
    customerEmail: booking.customer_email,
    icsAttachment: icsData,
  })
}
```

The `generateBookingICS` function in `src/lib/calendar.ts` must read `booking.sequence` and set it as the SEQUENCE field — not hardcode 0.

## Detection

- **ID:** `ics-modified-sequence`
- **Severity:** `high`
- **What to look for:** Find the booking modification code path. When a booking's date, time, or location changes, check whether: (1) A new .ics is generated and sent — search the modification handler for calendar generation calls. (2) The UID remains the same as the original (verify the same stable UID derivation is used). (3) A SEQUENCE field is incremented — look for a `sequence` column in the booking model/table and check it is incremented on modification. The SEQUENCE value in the .ics must match the stored value. (4) The updated .ics is attached to or linked from a modification email sent to the customer.
- **Pass criteria:** Enumerate all 4 conditions. ALL 4 must be met (minimum 4 of 4): (1) .ics is regenerated on modification — quote the function call. (2) UID is the same stable value — confirm the same derivation. (3) SEQUENCE is stored in the database and incremented by at least 1 per modification — quote the column and increment code. A missing SEQUENCE column does NOT count as pass. (4) Updated .ics is sent to the customer via email. Report the count: "4 of 4 conditions met" even on pass.
- **Fail criteria:** No .ics sent on modification. UID changes (customer sees a new event instead of an update). SEQUENCE not tracked or not incremented. Updated .ics generated but not sent to customer.
- **Skip (N/A) when:** Calendar invites not implemented (same criteria as ics-complete-uid skip), OR booking modifications are not supported (no modification handler found).
- **Detail on fail:** For each of the 4 conditions, state "MET" or "NOT MET" with evidence. Example: `"(1) .ics regenerated: MET — generateBookingICS() called in modifyBooking(). (2) UID stable: MET — same booking.id@domain. (3) SEQUENCE incremented: NOT MET — no sequence column in bookings table, SEQUENCE always 0. (4) .ics sent: MET — attached to modification email."`.
- **Remediation:** Track SEQUENCE in the database and increment on every modification:

  ```ts
  // src/lib/bookings.ts
  async function modifyBooking(bookingId: string, changes: Partial<Booking>) {
    const booking = await db.bookings.findUnique({ where: { id: bookingId } })

    // Increment SEQUENCE
    const updated = await db.bookings.update({
      where: { id: bookingId },
      data: { ...changes, sequence: (booking.sequence ?? 0) + 1 }
    })

    // Generate updated .ics with same UID, new SEQUENCE
    const icsData = await generateBookingICS(updated, booking.host)

    // Send to customer
    await emailQueue.add('send-modification-email', {
      bookingId,
      customerEmail: booking.customer_email,
      icsAttachment: icsData,
    })
  }
  ```
- **Cross-reference:** Booking Flow & Lifecycle audit checks modification state transitions. Booking Calendar Availability audit checks time slot recalculation. Database Audit checks schema migration for the sequence column.

## External references

- external RFC5545-SEQUENCE — https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.7.4

Taxons: data-integrity

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