Exceptions to recurring schedules override the rule in real time
Why it matters
A recurring schedule exception that does not override the calendar in real time is an invisible lie. The host marks next Monday as closed; the customer sees open slots for next Monday; the booking confirms; the host is unavailable. This ISO 25010 functional-correctness failure occurs when the exception record is stored correctly but the slot generation query does not join against the exceptions table before returning availability. The result is phantom slots: the system thinks it is correctly managing exceptions, but customers book into them anyway.
Severity rationale
Low because the failure requires a recurring-schedule-plus-exceptions setup to trigger, but when it does, it creates confirmed bookings on dates the host explicitly marked unavailable.
Remediation
In your slot generation function (typically src/lib/availability.ts or your availability API route), query exceptions in the same request as the recurring rule and filter before returning results.
// src/lib/availability.ts
export async function getRecurringSlots(recurringId: string, start: Date, end: Date) {
const [rec, exceptions] = await Promise.all([
db.recurringAvailability.findUnique({ where: { id: recurringId } }),
db.availabilityExceptions.findMany({
where: { recurringId, exceptionDate: { gte: start, lte: end } },
}),
])
if (!rec) return []
const blocked = new Set(exceptions.map(e => e.exceptionDate.toISOString().slice(0, 10)))
const occurrences = rrulestr(rec.rrule, { dtstart: start }).between(start, end)
return occurrences.filter(d => !blocked.has(d.toISOString().slice(0, 10)))
}
The exception filter must run synchronously in the slot generation path, not as a background job or deferred reconciliation task.
Detection
-
ID:
exception-handling -
Severity:
low -
What to look for: Count all code paths that generate slots from recurring rules. For each, verify that exception dates are queried and filtered before returning availability. Look for an exceptions table (e.g.,
availability_exceptions,schedule_exceptions) or an exceptions array within the recurring rule model. Trace the slot generation flow: RRULE parse -> generate occurrences -> filter by exceptions -> return available slots. Examine files matchingsrc/lib/*recurring*,src/lib/*schedule*,src/lib/*availability*,src/app/api/availability*. -
Pass criteria: Count all slot generation paths from recurring rules. 100% of slot generation paths must query and apply exceptions before returning slots. At least 1 exception storage mechanism must exist (database table, JSON array, or exception column). The exception filter must run at query time, not as a deferred background job. Report: "X of Y recurring slot paths apply exception filters. Exception storage: [table/array/column]."
-
Fail criteria: Calendar shows the default recurring pattern even on exception dates, or no exception querying exists in the slot generation code.
-
Skip (N/A) when: Platform does not support recurring schedules, or recurring schedules do not support exceptions.
-
Detail on fail: Example:
"Recurring schedule: Mon-Fri 9-5. Exception added: next Monday, closed. Calendar still shows Mon-Fri 9-5 for next Monday." -
Remediation: Apply exceptions when fetching slots in
src/lib/availability.tsor your slot-generation service. Query theavailability_exceptionstable and filter before returning:const exceptions = await db.availabilityExceptions.findMany({ where: { recurringId, exceptionDate: { gte: start, lte: end } }, }) const blocked = new Set(exceptions.map(e => e.exceptionDate.toISOString())) return slots.filter(s => !blocked.has(s.date.toISOString())) -
Cross-reference: Check
rule-based-storage— rule-based storage is the prerequisite; exceptions override specific instances of the rule. -
Cross-reference: Check
recurring-edit-scope— "edit this only" creates an exception; this check verifies exceptions are applied. -
Cross-reference: Check
data-freshness— exception changes must be reflected on the next calendar load.
External references
- iso-25010:2011 · functional-correctness — Functional Correctness — exceptions must override recurring rules in real time
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-calendar-availability·automated