Skip to content

[Big Pickle][booking] Booking Lifecycle & Event Planning Verdict — 11 new findings (waitlist, no-show, retirement, shrink-reallocation, COUNTERED deadline, ghost-hold, doc drift, cancellation-policy denormalization, partial-tier refund, dispute bypass, refund timeout) #1503

Description

@teetangh

Summary

Big Pickle verdict on the booking lifecycle & event planning slice — the umbrella over 11 newly-filed findings across rescheduling, cancellation, reallocation, event/plan planning, and refund design, plus the waitlist decision. Complements #1433 (booking system HLD/LLD verdict, 10 findings) and #1421 (finance verdict). Everything here is new code-grounded verification from the lifecycle pass.

The waitlist verdict

Two distinct concepts, settled (see #1492):

  • The existing Waitlist model (prisma/schema.prisma:4130-4158) is newsletter/lead-capture (email + DPDP consent + EVENT_SOLD_OUT source) — a pre-launch scarcity/email-build mechanic. Keep it.
  • There is no bookable-slot waitlist (per-slot FIFO queue with auto-promotion). Do not build it pre-MVP — it needs queue ordering, claim-window crons, auto-promotion sweeps = real baggage for a launch unlikely to have sold-out events on day one.
  • Pre-MVP scarcity = reuse EVENT_SOLD_OUT as a notify-me hook (zero new schema). Defer a real slot-queue post-MVP, gated on recurring sell-outs + cancel batching. Any future queue must be a denormalized typed model, not JSON (schema directive).

The 11 new findings

# Finding Sev
#1492 No bookable-slot waitlist — light EVENT_SOLD_OUT hook now, denormalized slot-queue post-MVP P3
#1493 No no-show automation — no NO_SHOW state, no auto-close on absent attendance, no money/credit reconciliation (revives #471) P2
#1494 Sold plans can't be retired — no consultant PATCH archivedAt on webinar/class; booked 1:1 plans can't retire-and-stop-new-bookings P2
#1495 No reallocation on availability shrink — edited-away availability orphans SCHEDULED bookings (#1206 is the creation direction only) P2
#1496 COUNTERED reschedule has no dedicated deadline — counter-round runs on the original 72h creation timer P2
#1497 Reschedule rate-limit is global-per-user (no per-appointment cap); openForAppointmentId ghost hold until hourly sweep P2
#1498 Reschedule/cancellation docs drifted — assert unenforced counter deadline, omit rate-limit shape, imply structured policy from free text P3
#1499 Org cancellation policy free-text + Appointment.cancellationPolicySnapshot is Json — denormalize to typed/versioned policy (schema directive) P2
#1500 Partial-tier refund undefined for credit-funded bookings — free credit has no cost basis (#1161 → MANUAL_REVIEW) P2
#1501 Dispute gate admin bypass TODO (#1319) ownerless since umbrella closed — needs named, audited carve P2
#1502 Refund subsystem lacks timeout budget, atomic refund+reallocation, and priority P2

Cross-cutting themes

  1. Money-planning boundary: [finance][P2 MEDIUM] Org cancellation policy is free-text + Appointment.cancellationPolicySnapshot is Json — denormalize to a typed, versioned policy model (schema directive: no JSON) #1499 + [finance][P2 MEDIUM] Partial-tier refund undefined for credit-funded bookings: free credit has no per-booking cost basis, so a partial session refund cannot be quoted structurally (#1161 left this at MANUAL_REVIEW) #1500 + [finance][P2 MEDIUM] Refund subsystem lacks timeout budget, atomic refund+reallocation, and priority: hanging provider calls can strand a pending refund, settled money and unbookable slot can drift, batch can starve urgent #1502 all touch the refund/credit quote path — they must stay inside the booking-money-boundary invariants (one refund quote, tentative hold / Payment.expiresAt, EXPIRED vs REJECTED vs CANCELLED). Any fix here is money-first; no backfill of past refunds (no-backfill posture).
  2. CAS + locks: [booking][P2 MEDIUM] COUNTERED reschedule has no dedicated deadline: counter-round runs on the original 72h creation timer, so a late counter can expire near-instantly or linger — no runway guard on the PENDING_REVIEW->COUNTERED edge #1496, [booking][P2 MEDIUM] Reschedule rate-limit is global-per-user (no per-appointment cap) and openForAppointmentId can act as a ghost hold until the hourly expiry sweep releases it #1497, [finance][P2 MEDIUM] Refund subsystem lacks timeout budget, atomic refund+reallocation, and priority: hanging provider calls can strand a pending refund, settled money and unbookable slot can drift, batch can starve urgent #1502 all move state on appointments/reschedules/refunds — they must use the seven CAS helpers, the Redis lock order, CAS-in-WHERE, and Serializable retries (booking-concurrency / booking-doctrine). [finance][P2 MEDIUM] Refund subsystem lacks timeout budget, atomic refund+reallocation, and priority: hanging provider calls can strand a pending refund, settled money and unbookable slot can drift, batch can starve urgent #1502's "refund + reallocation atomic" is explicitly a lock-envelope requirement.
  3. Cron shape: [booking][P2 MEDIUM] No no-show automation: SCHEDULED appointments never auto-close on absent attendance, no NO_SHOW state, no money/credit reconciliation (revives #471) #1493 (no-show) and parts of [booking][P2 MEDIUM] Reschedule rate-limit is global-per-user (no per-appointment cap) and openForAppointmentId can act as a ghost hold until the hourly expiry sweep releases it #1497 (ghost-hold sweep) extend the existing sweep pattern (expire-reschedule-proposals, expire-stale-requests) — idempotent, cron-locked, fail-open-with-error-set.
  4. Schema directive: no JSON unless necessary; denormalize. Appointment.cancellationPolicySnapshot Json? ([finance][P2 MEDIUM] Org cancellation policy is free-text + Appointment.cancellationPolicySnapshot is Json — denormalize to a typed, versioned policy model (schema directive: no JSON) #1499), the credit cost-basis gap ([finance][P2 MEDIUM] Partial-tier refund undefined for credit-funded bookings: free credit has no per-booking cost basis, so a partial session refund cannot be quoted structurally (#1161 left this at MANUAL_REVIEW) #1500), and any future waitlist ([booking][P3 LOW] No bookable-slot waitlist — only a newsletter Waitlist exists; use EVENT_SOLD_OUT notify-me pre-MVP, defer a denormalized slot-queue post-MVP #1492) are all governed by this.
  5. Pre-MVP vs post-MVP: money-correctness and availability-integrity findings ([finance][P2 MEDIUM] Org cancellation policy is free-text + Appointment.cancellationPolicySnapshot is Json — denormalize to a typed, versioned policy model (schema directive: no JSON) #1499, [finance][P2 MEDIUM] Partial-tier refund undefined for credit-funded bookings: free credit has no per-booking cost basis, so a partial session refund cannot be quoted structurally (#1161 left this at MANUAL_REVIEW) #1500, [finance][P2 MEDIUM] Dispute gate admin bypass TODO (#1319) now ownerless since the umbrella closed — privileged move of DISPUTE_ACTIVE appointments needs a named, audited, reviewed carve, not an undocumented hole #1501, fix(booking): auto-complete hands an unattended consultation to the no-show detector instead of closing it first (#1504) #1505-adjacent reallocation, [booking][P2 MEDIUM] No reallocation on availability shrink: cut/edited-away availability orphans SCHEDULED bookings with no re-propose or mass-reschedule affordance (#1206 covers the creation direction only) #1495, [booking][P2 MEDIUM] Sold plans can't be retired: sole-owner webinar/class routes refuse DELETE but have no consultant PATCH archivedAt writer; booked 1:1 plans have no retire-and-stop-new-bookings path #1494, [booking][P2 MEDIUM] COUNTERED reschedule has no dedicated deadline: counter-round runs on the original 72h creation timer, so a late counter can expire near-instantly or linger — no runway guard on the PENDING_REVIEW->COUNTERED edge #1496, [booking][P2 MEDIUM] Reschedule rate-limit is global-per-user (no per-appointment cap) and openForAppointmentId can act as a ghost hold until the hourly expiry sweep releases it #1497) are launch: pre-mvp; polish/automation/doc gaps ([booking][P2 MEDIUM] No no-show automation: SCHEDULED appointments never auto-close on absent attendance, no NO_SHOW state, no money/credit reconciliation (revives #471) #1493 no-show, [booking][P3 LOW] Reschedule/cancellation docs drifted: assert a per-round counter deadline that isn't enforced (single 72h timer), omit the global rate-limit shape, and imply a structured refund policy from free text #1498 doc drift, waitlist queue) are launch: post-mvp.

Verified code anchors (all read directly this pass)

  • prisma/schema.prisma:4130-4158Waitlist (newsletter) + EventSoldOut note + cancellationPolicySnapshot Json? at :4186
  • prisma/schema.prisma:3677-3688AppointmentStatus (no NO_SHOW)
  • lib/booking/transitions.ts:474-494RESCHEDULE_ALLOWED_FROM, COUNTERED single-arrow, RESCHEDULE_OPEN_STATUSES
  • lib/booking/reschedule-proposals.ts:24PROPOSAL_MAX_LIFETIME_HOURS = 72 single timer
  • scripts/appointments/expire-reschedule-proposals.ts — sweeps open statuses by expiresAt, clears openForAppointmentId
  • lib/rate-limit.ts:174eventMutationLimiter 10/min/user shared across event mutations
  • app/api/appointments/[appointmentId]/reschedule/route.ts:93,598,946 — limiter keying + openForAppointmentId reservation + #1319 comment
  • app/api/plans/webinars/[webinarPlanId]/route.ts:62, classes/[classPlanId]/route.ts:62 — DELETE refused, no consultant archive writer
  • app/api/organizations/[orgId]/catalog/route.ts:303-329 — only bulk archivedAt writer (org scope)
  • utils/slotAllocation/*, jobs/appointments/reconcile-slot-availability.ts — no shrink reallocation
  • lib/stream/call-presence.ts + session-handlers.ts + webhook-events.ts — raw no-show signals, unconsumed
  • lib/payments/operations/refund.ts — refund flow (holds, expiry, MANUAL_REVIEW partial-tier)

Related

Suggested sequencing

  1. Money-first, pre-MVP: [finance][P2 MEDIUM] Org cancellation policy is free-text + Appointment.cancellationPolicySnapshot is Json — denormalize to a typed, versioned policy model (schema directive: no JSON) #1499[finance][P2 MEDIUM] Partial-tier refund undefined for credit-funded bookings: free credit has no per-booking cost basis, so a partial session refund cannot be quoted structurally (#1161 left this at MANUAL_REVIEW) #1500[finance][P2 MEDIUM] Refund subsystem lacks timeout budget, atomic refund+reallocation, and priority: hanging provider calls can strand a pending refund, settled money and unbookable slot can drift, batch can starve urgent #1502 (refund/credit/policy), keep within booking-money-boundary.
  2. Availability integrity, pre-MVP: [booking][P2 MEDIUM] Sold plans can't be retired: sole-owner webinar/class routes refuse DELETE but have no consultant PATCH archivedAt writer; booked 1:1 plans have no retire-and-stop-new-bookings path #1494 (retirement) + [booking][P2 MEDIUM] No reallocation on availability shrink: cut/edited-away availability orphans SCHEDULED bookings with no re-propose or mass-reschedule affordance (#1206 covers the creation direction only) #1495 (shrink reallocation) + [booking][P2 MEDIUM] Reschedule rate-limit is global-per-user (no per-appointment cap) and openForAppointmentId can act as a ghost hold until the hourly expiry sweep releases it #1497 (ghost-hold) — all touch allocation/locks.
  3. Negotiation correctness, pre-MVP: [booking][P2 MEDIUM] COUNTERED reschedule has no dedicated deadline: counter-round runs on the original 72h creation timer, so a late counter can expire near-instantly or linger — no runway guard on the PENDING_REVIEW->COUNTERED edge #1496 (COUNTERED deadline) + [finance][P2 MEDIUM] Dispute gate admin bypass TODO (#1319) now ownerless since the umbrella closed — privileged move of DISPUTE_ACTIVE appointments needs a named, audited, reviewed carve, not an undocumented hole #1501 (dispute bypass).
  4. Post-MVP polish: [booking][P2 MEDIUM] No no-show automation: SCHEDULED appointments never auto-close on absent attendance, no NO_SHOW state, no money/credit reconciliation (revives #471) #1493 (no-show automation), [booking][P3 LOW] Reschedule/cancellation docs drifted: assert a per-round counter deadline that isn't enforced (single 72h timer), omit the global rate-limit shape, and imply a structured refund policy from free text #1498 (doc drift), [booking][P3 LOW] No bookable-slot waitlist — only a newsletter Waitlist exists; use EVENT_SOLD_OUT notify-me pre-MVP, defer a denormalized slot-queue post-MVP #1492 (waitlist queue deferral).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bookingBooking, scheduling, slots, reschedule, cancellationfinancePayments, refunds, earnings, payouts, invoicing, ledgerlaunch: pre-mvpGates launch — money, data, or a failure we would not detect

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions