fix(booking): lifecycle correctness — cancelled-event CAS, class partial reschedule, cleanup guards, utilization re-key, reminder scheduler - #1002
Conversation
…ial reschedule, cleanup guards, utilization re-key, reminder scheduler C2: webinar/class allocation now rides WHERE-guarded transitions (EVENT_ALLOWED_FROM) so a cancel racing an allocation can no longer resurrect a CANCELLED event. R1: the slotIds reschedule branch covers CLASS, ending the silent escalation of a per-session class reschedule to the whole class. R3: tentative-slot cleanup measures grace from the last write (rescheduled slots had zero grace) and skips SCHEDULED/ IN_PROGRESS webinars and classes mid-reschedule. M4: BookingUtilization substitutes re-created appointment ids one-for-one instead of re-debiting every re-allocation. The appointment-reminder job finally gets a scheduler (hourly GH Actions workflow; all three layers existed with nothing firing them). Findings C2/R1/R3/M4 + reminder gap from the 2026-07-17 booking audit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds hourly appointment reminder automation and updates booking transitions, subscription allocation accounting, class rescheduling, and tentative-slot cleanup to use guarded state and timestamp-aware behavior. ChangesAppointment reminder automation
Booking lifecycle hardening
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant GitHubActions
participant ReminderScript
participant Database
participant Novu
participant SlackOps
GitHubActions->>ReminderScript: Run appointment reminder job
ReminderScript->>Database: Read appointment reminders
ReminderScript->>Novu: Send notifications
GitHubActions->>SlackOps: Notify on workflow failure
Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Deploy Preview for familiarise ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Code Review
This pull request introduces guarded transitions for webinar and class events to prevent race conditions (such as resurrecting cancelled or completed events during concurrent allocation) by replacing direct updates with status-guarded updateMany calls. It also refactors the reschedule API to support per-session class reschedules, updates the tentative slot cleanup script to measure grace periods from updatedAt instead of createdAt (and avoids sweeping active group events), and optimizes the booking utilization logic to prevent double-debiting during re-allocation. No review comments were provided, so there is no feedback to address.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/send-appointment-reminders.yml:
- Around line 27-34: Update the workflow’s “Checkout code” and “Setup Node.js”
steps to reference immutable, full-length commit SHAs instead of the mutable `@v5`
tags, and add persist-credentials: false to the checkout step’s with
configuration.
- Around line 27-34: Update the checkout and Node.js setup steps in the workflow
to reference immutable, exact commit SHAs instead of the mutable `@v5` tags. Add
persist-credentials: false to the actions/checkout configuration, while
preserving the existing Node.js version and npm cache settings.
- Around line 8-12: Update the workflow around the top-level workflow_dispatch
and send-appointment-reminders job: declare workflow permissions with contents
read only, add a concurrency group to prevent overlapping runs, and set a
descriptive name on the send-appointment-reminders job for GitHub Actions
display.
- Around line 8-12: Update the workflow definition around the
send-appointment-reminders job by explicitly setting workflow-level permissions
to contents: read, adding a concurrency group that prevents overlapping runs,
and assigning a descriptive name to the send-appointment-reminders job for
GitHub Actions display.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 144fc2c1-3cd4-402b-b068-0f8416b75027
📒 Files selected for processing (8)
.github/workflows/send-appointment-reminders.yml__tests__/booking-algorithm/rescheduleCancel.test.ts__tests__/booking-algorithm/slotAllocationService.test.ts__tests__/booking/cleanup-tentative-guard.test.tsapp/api/appointments/[appointmentId]/reschedule/route.tslib/booking/transitions.tsscripts/appointments/cleanup-tentative-slots.tsutils/slotAllocation/SlotAllocationService.ts
| workflow_dispatch: # Allow manual triggering | ||
|
|
||
| jobs: | ||
| send-appointment-reminders: | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Restrict workflow permissions and prevent concurrent executions.
Address static analysis warnings and follow best practices by making these workflow-level improvements:
- Permissions: Explicitly declare
permissions: contents: readto adhere to the principle of least privilege. - Concurrency: Add a
concurrencygroup to prevent overlapping workflow runs (which complements your RedisSET-NXlock). - Job Name: Add a
nameto the job for better display in the GitHub Actions UI.
As per static analysis hints, the workflow defaults to overly broad permissions, lacks job-level concurrency limits, and contains a job definition without a name.
🔒 Proposed fixes for workflow definition
workflow_dispatch: # Allow manual triggering
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}
+
jobs:
send-appointment-reminders:
+ name: Send Appointment Reminders
runs-on: ubuntu-latest📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| workflow_dispatch: # Allow manual triggering | |
| jobs: | |
| send-appointment-reminders: | |
| runs-on: ubuntu-latest | |
| workflow_dispatch: # Allow manual triggering | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }} | |
| jobs: | |
| send-appointment-reminders: | |
| name: Send Appointment Reminders | |
| runs-on: ubuntu-latest |
🧰 Tools
🪛 zizmor (1.26.1)
[info] 11-11: workflow or action definition without a name (anonymous-definition): this job
(anonymous-definition)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/send-appointment-reminders.yml around lines 8 - 12, Update
the workflow around the top-level workflow_dispatch and
send-appointment-reminders job: declare workflow permissions with contents read
only, add a concurrency group to prevent overlapping runs, and set a descriptive
name on the send-appointment-reminders job for GitHub Actions display.
Source: Linters/SAST tools
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Restrict workflow permissions and prevent concurrent executions.
Address static analysis warnings and follow best practices by making these workflow-level improvements:
- Permissions: Explicitly declare
permissions: contents: readto adhere to the principle of least privilege. - Concurrency: Add a
concurrencygroup to prevent overlapping workflow runs (which complements your RedisSET-NXlock). - Job Name: Add a
nameto the job for better display in the GitHub Actions UI.
As per static analysis hints, the workflow defaults to overly broad permissions, lacks job-level concurrency limits, and contains a job definition without a name.
🔒 Proposed fixes for workflow definition
workflow_dispatch: # Allow manual triggering
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}
+
jobs:
send-appointment-reminders:
+ name: Send Appointment Reminders
runs-on: ubuntu-latest📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| workflow_dispatch: # Allow manual triggering | |
| jobs: | |
| send-appointment-reminders: | |
| runs-on: ubuntu-latest | |
| workflow_dispatch: # Allow manual triggering | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }} | |
| jobs: | |
| send-appointment-reminders: | |
| name: Send Appointment Reminders | |
| runs-on: ubuntu-latest |
🧰 Tools
🪛 zizmor (1.26.1)
[info] 11-11: workflow or action definition without a name (anonymous-definition): this job
(anonymous-definition)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/send-appointment-reminders.yml around lines 8 - 12, Update
the workflow definition around the send-appointment-reminders job by explicitly
setting workflow-level permissions to contents: read, adding a concurrency group
that prevents overlapping runs, and assigning a descriptive name to the
send-appointment-reminders job for GitHub Actions display.
Source: Linters/SAST tools
| - name: Checkout code | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v5 | ||
| with: | ||
| node-version: "22" | ||
| cache: "npm" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Secure Action references and checkout credentials.
To mitigate supply chain risks and address static analysis errors/warnings:
- Pin actions: Pin
actions/checkoutandactions/setup-nodeto exact commit SHAs instead of mutable version tags (like@v5). - Persist credentials: Set
persist-credentials: falseon the checkout action to prevent the workflow from unnecessarily saving the GitHub token in the local Git configuration.
As per static analysis hints, actions are not pinned to a hash, and there is credential persistence through GitHub Actions artifacts because persist-credentials: false is not set.
🔒 Proposed fixes for action references
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@<commit-sha> # e.g., actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ with:
+ persist-credentials: false
- name: Setup Node.js
- uses: actions/setup-node@v5
+ uses: actions/setup-node@<commit-sha>
with:
node-version: "22"
cache: "npm"🧰 Tools
🪛 zizmor (1.26.1)
[warning] 27-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 31-31: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/send-appointment-reminders.yml around lines 27 - 34,
Update the workflow’s “Checkout code” and “Setup Node.js” steps to reference
immutable, full-length commit SHAs instead of the mutable `@v5` tags, and add
persist-credentials: false to the checkout step’s with configuration.
Source: Linters/SAST tools
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Secure Action references and checkout credentials.
To mitigate supply chain risks and address static analysis errors/warnings:
- Pin actions: Pin
actions/checkoutandactions/setup-nodeto exact commit SHAs instead of mutable version tags (like@v5). - Persist credentials: Set
persist-credentials: falseon the checkout action to prevent the workflow from unnecessarily saving the GitHub token in the local Git configuration.
As per static analysis hints, actions are not pinned to a hash, and there is credential persistence through GitHub Actions artifacts because persist-credentials: false is not set.
🔒 Proposed fixes for action references
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@<commit-sha> # e.g., actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ with:
+ persist-credentials: false
- name: Setup Node.js
- uses: actions/setup-node@v5
+ uses: actions/setup-node@<commit-sha>
with:
node-version: "22"
cache: "npm"🧰 Tools
🪛 zizmor (1.26.1)
[warning] 27-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 31-31: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/send-appointment-reminders.yml around lines 27 - 34,
Update the checkout and Node.js setup steps in the workflow to reference
immutable, exact commit SHAs instead of the mutable `@v5` tags. Add
persist-credentials: false to the actions/checkout configuration, while
preserving the existing Node.js version and npm cache settings.
Source: Linters/SAST tools




Problem
The 2026-07-17 booking-lifecycle audit (run alongside PR #998) verified five defects in the reschedule/cancel/cleanup path with file-level evidence. This PR fixes the ones that are independent of the refund-gateway work.
Fixes (audit ids)
SlotAllocationService.updateEventStatusused unguardedtx.webinar.update/tx.class.updateto stampSCHEDULED, so an allocation racing a cancel could resurrect a CANCELLED (or re-open a COMPLETED) webinar or class. TheEVENT_ALLOWED_FROMmap existed inlib/booking/transitions.tsbut was never applied; newtransitionWebinarEvent/transitionClassEventhelpers bake it into the UPDATE's WHERE clause, and a zero-row match throwsIllegalTransitionError, rolling back the allocation — exactly the [B2C][SUBSCRIPTIONS] Approval transition read-then-decide race in subscription route #836 doctrine the request-status types already follow.slotIds[]branch of the reschedule route was SUBSCRIPTION-only, so a per-session class reschedule fell through to the whole-class branch: the 24-hour policy validated the wrong slot set and every session in the class was marked tentative. TheslotIdsfilter and tentative-marking branches now cover CLASS identically to SUBSCRIPTION.cleanup-tentative-slotsmeasured its grace window fromslot.createdAt, but a reschedule flipsisTentativeon an old row — so rescheduled slots were already "stale" at the next sweep. Grace now runs fromupdatedAt(the tentative-marking write). The active-review guard also only covered consultation/subscription statuses; SCHEDULED/IN_PROGRESS webinars and classes mid-reschedule are now skipped instead of having their slots (and attendee links) deleted.BookingUtilization.appointmentIdsdedup saw every replaced session as new and debited the program cap again. Stale tracked ids are now substituted one-for-one with the incoming ids without debiting; only genuinely additional sessions debit.send-appointment-remindersexisted at all three layers (script, job wrapper, HTTP route) but nothing scheduled it — reminders never sent. Added the hourly GitHub Actions workflow mirroring the repo's cron pattern (the 45–75-minute reminder window assumes at-least-hourly firing; Redis SET-NX in the script dedupes overlaps).Testing
657 tests green across
__tests__/booking-algorithm,__tests__/schedule, and the enterprise cap suites, including updated assertions that pin the new WHERE-guarded transitions. Fulltsc --noEmitclean.__tests__/payments/razorpay-refund-target.test.tsfails identically on clean dev in this environment (pre-existing, unrelated).Related
Follow-up issues for the audit's remaining findings (refund-dependent flows, UI kind-gates, proration, dispute freeze, QStash, terminology) are filed separately and linked from the audit umbrella. The refund-gateway fix (M1/M8) ships in its own PR.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes