This is the master tracker for the booking + maintenance productionization train. It consolidates a five-lens audit (Prisma schema + allocation engine, reschedule/cancel lifecycle + cron fleet + heatmap, B2B/org booking + guests/collaborators + docs, GitHub-issues archaeology with closed-fix regression checks, and a full frontend/UX journey walk) run 2026-08-13/14 against dev. Every finding below was verified against source with file:line evidence; the load-bearing P0s were re-verified by hand before this issue was filed.
1. Context and goal
The booking subsystem (consultation / subscription / webinar / class + trials; auto / manual / requested allocation — the mode formerly named preAllocate; request-for-approval and direct checkout; B2C and org-funded payment rails; the reschedule/cancel lifecycle) and the maintenance subsystem (maintenance mode OFF/DEGRADED/OFFLINE plus the 61-workflow scheduled fleet) are MVP-grade. The goal of this train is 100% production readiness for both, delivered as ten PRs in four serial waves cut from dev, with one coordinated db push at the end (gated on #1045) and the docs/prompts corpus brought back in sync.
Three prior hardening waves left the money paths in good shape, and the regression check confirmed every closed booking fix still holds (#1003, #1004, #1005, #1012, #1071 via PR #1091; #829; #830; #835 via #854; #440; #476; #503 items 1–5). This train is new work, not re-work.
2. Findings register
P0 — money loss / integrity
| ID |
Finding |
Evidence |
| P0-1 |
Maintenance freeze hard-deletes appointments whose only Payment is PENDING (the include is filtered to SUCCEEDED, the guard checks payment.length === 0), cascade-destroying the Payment row; the capture webhook then lands on nothing |
actions/maintenance/freeze-appointments.ts:148,371 |
| P0-2 |
Freeze refunds payment.amount gross via raw createRefund, bypassing the refundable-balance clamp; org_* and free_ intents throw UNKNOWN_GATEWAY, leaving no wallet/ledger/seat reversal |
freeze-appointments.ts:390-394 |
| P0-3 |
Freeze skips every CAS (it can resurrect COMPLETED→CANCELLED), hard-deletes slots, and never closes open RescheduleRequests; refundWholeEventPayments is explicitly non-idempotent, so freeze + manual cancel double-refunds every attendee |
freeze-appointments.ts:361-372, lib/payments/operations/event-refunds.ts:40-43 |
| CORE-1 |
Trial slots are created without consultantProfileId, so they fall outside the slot_no_confirmed_overlap exclusion constraint — a trial and a consultation can confirm on the same consultant-minute (#1093 §1) |
app/api/trials/[trialId]/route.ts:378-398 |
| CORE-2 |
Four disjoint Redis lock namespaces guard the same physical resource, and keys are start-instant-granular rather than interval-granular; the trial path never serializes against checkout or allocation |
utils/appointmentlock.ts:391,540,588,639 |
| CORE-3 |
createAppointmentForConsultation fabricates a confirmed slot at now+1h with no availability/conflict/lock check, no consultantProfileId, one long slot instead of 30-minute atoms — using the global Prisma client from inside a Serializable transaction, so the row survives the transaction's rollback |
app/api/bookings/consultations/[consultationId]/route.ts:913-970 |
| UX-1..5 |
The paid-trial modal never shows the price; the consultant's "propose times" toast claims the consultee has been asked to accept when no consultee surface renders proposals at all; remove-attendee is one-click, unconfirmed, and discards the refund summary; the cancel dialog asks for consent without a refund preview; abandoning the Razorpay modal dead-ends (no ondismiss) |
TrialBookingModal.tsx:189-236, useConsultantEventActions.ts:106-111, participants page :86-91,182-191, CancelConfirmationDialog.tsx:82-95, RazorpayCheckout.tsx:152-214 |
Two further money-integrity items from the enterprise audit's private advisory (the withheld sections referenced by #1132) are absorbed by wave 1; their details are deliberately not restated here.
P1 (the full list rides in the PR descriptions)
The reschedule response loop was never built — ACCEPTED/DECLINED/COUNTERED are dead or side-effect-only enum members, the one DECLINED writer bypasses the CAS helper, and the withdraw endpoint has zero UI callers (#1163). Org admins can neither cancel nor reschedule org-funded bookings, and the approval flow drops org sponsorship entirely (#1166). Subscription cancellation after any delivered session refunds ₹0 into a MANUAL_REVIEW queue nobody drains (#1006, now unmasked). expire-unpaid-trials races the capture webhook and skips the tombstone — and the live cron entrypoints are jobs/**, with scripts/ twins that are stale diverged copies to delete. Roughly 21 scheduled jobs run without withCronLock, several of them FINANCIAL_JOB_NAMES members that the doctrine says must fail closed. The 1-hour reminder window is 30 minutes wide on a fleet measured to fire ~every 2.75 hours (#866). The checkout Serializable transaction is not retried on P2034, so hot-webinar contention surfaces as "Something Went Wrong". The approval path holds a gateway round-trip inside its Serializable transaction, re-confirms RESCHEDULED slots, and mints on a divergent gateway (#1165). The free_ referral-credit refund rail dead-ends (#1161). Abandoned-checkout cleanup cascades away BookingUtilization while engagementsUsed stays incremented, shrinking org seat caps (#1132 §08). The org/personal scoping seam has nine violations (#1166). The constraint sidecar is applied by no CI or deploy step, so a future db push would silently drop the double-booking backstop (#1092). Razorpay success skips the checkout-success reconciliation poll that the Stripe path gets. The subscription reschedule grid is unclamped (no period window, no weekly caps), and the trial reschedule page renders fully then 403s at submit.
P2
Unbounded janitor scans (cleanup-tentative-slots has no take; the freeze scan runs a 5-level include in one 60-second transaction) against the good MAX_EVENTS_PER_RUN = 5000 pattern; webinar/class tentative slots are excluded from reconcile and never self-heal; the cancel audit log collapses platform actors to consultant|consultee; cancelled/refunded attendees keep Stream channel access; the grid footer names only the browser timezone while caps bucket in schedulingTimezone (#1076); org surfaces hardcode Asia/Kolkata while personal surfaces render viewer-local; assorted toast/empty-state/pagination polish.
Verified good — do not "fix"
Interactive cancel paths are CAS-guarded with frozen refund snapshots (100/50/0 tiers; consultant-initiated = 100%). The allocation engine's core is sound: idempotent replay via allocationIdempotencyKey checked both pre-lock and in-lock, consistent consultant→consultee lock ordering, stale-tab CAS via expectedTentativeSlotCount, in-transaction conflict revalidation plus pg_advisory_xact_lock, and 23P01/23505 mapped to 409s. Auto-confirm re-validates proposals through the full allocator under wideLock — nothing is trusted from the client, and the ~4,600-line client engine is already demoted to display + test oracle. The org scope helpers fail closed with no cross-tenant IDOR. The payout service holds its own fail-closed distributed lock (the comment in system-jobs/run is accurate). The outbox drains are locked and bounded, org invitations are complete end-to-end, and Waitlist is the newsletter by design — event capacity has no waitlist, and a full event is simply sold out.
3. The PR train (10 PRs, 4 serial waves, nothing stacked)
| # |
Branch |
Thesis |
Carries |
| 1 |
fix/booking-slot-integrity |
Trials enter the same lock/constraint/validation guarantees as everything else; lock keys become interval-granular |
CORE-1, CORE-2, trial validation, delete-branch guard, breaker gating, capacity include-trap, deterministic org-payment pick — Part of #1093, #676 |
| 2 |
fix/approval-path-correctness |
Approval can no longer invent appointments, drop org context, or hold a gateway call inside a transaction |
CORE-3, ORG-9, closes #1165, RESCHEDULED exclusion, withSerializableRetry on checkout, tentative-hold visibility in the conflict check |
| 3 |
fix/refund-doctrine-and-freeze |
One refund front door all rails actually enter; freeze obeys the doctrine |
P0-1/2/3, closes #1161 and #1162, wallet-receivable decision, #676 B1 reconcile-by-UUID, event-refund idempotency, webhook idempotency-key precedence, maintenance-key TTL (#697 INF-1) |
| 4 |
fix/reschedule-cancel-lifecycle |
The proposal loop closes; lifecycle authorization, proration, and audit stop lying |
Closes #1163, #448 (rescoped), #1006, #1085 in-repo residue; org-admin cancel/reschedule; program-window awareness; Stream channel cleanup |
| 5 |
fix/org-scoping-seam |
Org and personal contexts stop bleeding into each other |
Closes #1166 (ORG-1..8; ORG-9 rides PR 2) |
| 6 |
fix/cron-fleet-hygiene |
Every job locked, bounded, doctrine-compliant, and able to notice its own death |
Locks on ~21 jobs, bounded scans, expire-unpaid-trials fix, reminder windows, #1132 §08, heartbeat dead-man workflow, minimal SystemJobExecution writes, stale scripts/ twin deletion — Part of #866, #697, #1132 |
| 7 |
fix/booking-ux-money-truth |
Every destructive or paid action shows its price first; no payment flow dead-ends |
UX-1..5, closes #1167, checkout-success parity, capacity re-check, subject clamps, cache invalidation |
| 8 |
perf/scheduling-freshness-and-diet |
The grid stops being permanently stale; the dead client engine is deleted |
Closes #1164 and #1076 (mitigation half; full fix is #1168), real-service parity test, PENDING-list reshape — Part of #997 |
| 9 |
fix/db-booking-constraints |
Every documented-but-absent DB guarantee becomes real, mapped to one push |
Closes #1093 (§2 decision recorded: receivable); §3 server-minted idempotency keys, §4 partial uniques, §5 exclusion (staged), indexes, sidecar-drift CI guard — Part of #1092 |
| 10 |
docs/booking-maintenance-refresh |
The residual docs debt no code PR owns |
Closes #1013; docs/collaborators de-drift, prompts/ index + seed-cohort reconciliation + refreshed booking test prompts |
Post-train: fix/tz-grid-scheduling-timezone delivers #1168. Merge order is strictly serial 1→10; the one coordinated db push (then npm run db:sidecars) happens after PR 9 merges and #1045 has merged.
4. Decisions recorded (2026-08-13)
Heatmap freshness = client polling (~60s + focus refetch) with s-maxage on the availability endpoints; SSE rejected on Netlify's function timeout; Supabase Realtime assessed viable-but-not-now (Broadcast-mode invalidation pings are the upgrade path once a live "seats left" surface justifies quotas, after #481). No server-action conversion — the Route Handler + useMutation convention stands. Wallet floor stays >= 0; the top-up-refund shortfall books an org receivable. #1076 ships the explain-the-bucket mitigation now, #1168 later. Approval links unify on RAZORPAY. Org context is carried end-to-end through approvals. The train-end push is additive; NOT-NULL flips and clean-data constraints stay staged for the pre-MVP reset.
5. Existing-issue dispositions
#676 is superseded by this tracker (A1–A4 shipped via #847 and live-verified; B1's reconcile-by-UUID half closes via PR 3; docs drift folds into #1013/PR 10; the AE/CN/CA/RV/A11 addendum items get re-triaged after the train). #837's checkboxes are decorative — its comments are the record — and its one remaining item, the staging chaos go/no-go, merges with #874 as this train's exit gate. #309 closes as a duplicate of #997. #448 is rescoped to its audit-trail and staff-blind-spot halves and closes via PR 4. #1092 keeps the observability build-out; PR 9 carries only the drift-guard slice. #866 keeps the QStash decision with #1010 as the execution issue; PR 6 carries the heartbeat and locks. #697 keeps INF-3/4/5; INF-1 and INF-2-minimal ride PRs 3 and 6. #872 owns the DST root cause and builds on #1168. #1132 keeps its auth/SSRF/GST items; PR 6 carries §08 and wave 1 absorbs its two withheld booking/money items. #867's booking-subset items are absorbed here; the dashboard-wide remainder stays. #472, #1058, #1089, and #1128 are linked as adjacent, not carried.
6. Verification gate
Per-PR: tsc, eslint (warnings block), targeted jest, and a background dev server exercised via mock-data API scripts. New suites: freeze-doctrine, trial-slot-integrity, accept/decline transitions, free_-rail + event-refund idempotency, org attendee-arm + list/detail parity, cron-lock registry drift, checkout price parity, and a real-service grid parity test replacing the self-mocking one. Train-wide: chaos scenarios 1/2/4 plus a new trial-vs-checkout same-minute race script after wave 1; npm run test:chaos:api plus scenario 5 after wave 2; the coordinated push + Supabase advisors after PR 9; and the full 8-scenario staging chaos run (the #837/#874 go/no-go) — extended with freeze-during-pending-capture and freeze-then-manual-cancel scenarios — as the train exit gate.
7. Deferral register (explicitly out of this train)
QStash execution (#1010); the full #997 engine port; DST (#872) and #1168 (post-train); SystemJobExecution dashboards (#1092); INF-3/4/5; no-show marking UI; guest/magic-link access; event seat-waitlist; the trial-reschedule feature (PR 7 hides the broken entry); collaborator permission enforcement (#768); the org trials dead API surface (delete-or-build pending); #906 and the cold-start cluster (#932/#937/#1117/#1120/#1124 — a separate infra train); #1132 §05 GST credit notes; MAX_CANDIDATE_STARTS_PER_ROW; remaining UX P2 polish. #481 and #874 run as launch gates at train exit rather than as train content.
PR checklist
Related: #1161 #1162 #1163 #1164 #1165 #1166 #1167 #1168 · #676 #837 #866 #697 #997 #1006 #1013 #1076 #1092 #1093 #1132 #448 #1085 #1010 #872 #874 #481 · Part of #1072.
1. Context and goal
The booking subsystem (consultation / subscription / webinar / class + trials;
auto/manual/requestedallocation — the mode formerly namedpreAllocate; request-for-approval and direct checkout; B2C and org-funded payment rails; the reschedule/cancel lifecycle) and the maintenance subsystem (maintenance mode OFF/DEGRADED/OFFLINE plus the 61-workflow scheduled fleet) are MVP-grade. The goal of this train is 100% production readiness for both, delivered as ten PRs in four serial waves cut fromdev, with one coordinateddb pushat the end (gated on #1045) and the docs/prompts corpus brought back in sync.Three prior hardening waves left the money paths in good shape, and the regression check confirmed every closed booking fix still holds (#1003, #1004, #1005, #1012, #1071 via PR #1091; #829; #830; #835 via #854; #440; #476; #503 items 1–5). This train is new work, not re-work.
2. Findings register
P0 — money loss / integrity
payment.length === 0), cascade-destroying the Payment row; the capture webhook then lands on nothingactions/maintenance/freeze-appointments.ts:148,371payment.amountgross via rawcreateRefund, bypassing the refundable-balance clamp;org_*andfree_intents throwUNKNOWN_GATEWAY, leaving no wallet/ledger/seat reversalfreeze-appointments.ts:390-394refundWholeEventPaymentsis explicitly non-idempotent, so freeze + manual cancel double-refunds every attendeefreeze-appointments.ts:361-372,lib/payments/operations/event-refunds.ts:40-43consultantProfileId, so they fall outside theslot_no_confirmed_overlapexclusion constraint — a trial and a consultation can confirm on the same consultant-minute (#1093 §1)app/api/trials/[trialId]/route.ts:378-398utils/appointmentlock.ts:391,540,588,639createAppointmentForConsultationfabricates a confirmed slot at now+1h with no availability/conflict/lock check, noconsultantProfileId, one long slot instead of 30-minute atoms — using the global Prisma client from inside a Serializable transaction, so the row survives the transaction's rollbackapp/api/bookings/consultations/[consultationId]/route.ts:913-970ondismiss)TrialBookingModal.tsx:189-236,useConsultantEventActions.ts:106-111, participants page:86-91,182-191,CancelConfirmationDialog.tsx:82-95,RazorpayCheckout.tsx:152-214Two further money-integrity items from the enterprise audit's private advisory (the withheld sections referenced by #1132) are absorbed by wave 1; their details are deliberately not restated here.
P1 (the full list rides in the PR descriptions)
The reschedule response loop was never built —
ACCEPTED/DECLINED/COUNTEREDare dead or side-effect-only enum members, the oneDECLINEDwriter bypasses the CAS helper, and the withdraw endpoint has zero UI callers (#1163). Org admins can neither cancel nor reschedule org-funded bookings, and the approval flow drops org sponsorship entirely (#1166). Subscription cancellation after any delivered session refunds ₹0 into aMANUAL_REVIEWqueue nobody drains (#1006, now unmasked).expire-unpaid-trialsraces the capture webhook and skips the tombstone — and the live cron entrypoints arejobs/**, withscripts/twins that are stale diverged copies to delete. Roughly 21 scheduled jobs run withoutwithCronLock, several of themFINANCIAL_JOB_NAMESmembers that the doctrine says must fail closed. The 1-hour reminder window is 30 minutes wide on a fleet measured to fire ~every 2.75 hours (#866). The checkout Serializable transaction is not retried on P2034, so hot-webinar contention surfaces as "Something Went Wrong". The approval path holds a gateway round-trip inside its Serializable transaction, re-confirms RESCHEDULED slots, and mints on a divergent gateway (#1165). Thefree_referral-credit refund rail dead-ends (#1161). Abandoned-checkout cleanup cascades awayBookingUtilizationwhileengagementsUsedstays incremented, shrinking org seat caps (#1132 §08). The org/personal scoping seam has nine violations (#1166). The constraint sidecar is applied by no CI or deploy step, so a futuredb pushwould silently drop the double-booking backstop (#1092). Razorpay success skips the checkout-success reconciliation poll that the Stripe path gets. The subscription reschedule grid is unclamped (no period window, no weekly caps), and the trial reschedule page renders fully then 403s at submit.P2
Unbounded janitor scans (
cleanup-tentative-slotshas notake; the freeze scan runs a 5-level include in one 60-second transaction) against the goodMAX_EVENTS_PER_RUN = 5000pattern; webinar/class tentative slots are excluded from reconcile and never self-heal; the cancel audit log collapses platform actors to consultant|consultee; cancelled/refunded attendees keep Stream channel access; the grid footer names only the browser timezone while caps bucket inschedulingTimezone(#1076); org surfaces hardcode Asia/Kolkata while personal surfaces render viewer-local; assorted toast/empty-state/pagination polish.Verified good — do not "fix"
Interactive cancel paths are CAS-guarded with frozen refund snapshots (100/50/0 tiers; consultant-initiated = 100%). The allocation engine's core is sound: idempotent replay via
allocationIdempotencyKeychecked both pre-lock and in-lock, consistent consultant→consultee lock ordering, stale-tab CAS viaexpectedTentativeSlotCount, in-transaction conflict revalidation pluspg_advisory_xact_lock, and 23P01/23505 mapped to 409s. Auto-confirm re-validates proposals through the full allocator underwideLock— nothing is trusted from the client, and the ~4,600-line client engine is already demoted to display + test oracle. The org scope helpers fail closed with no cross-tenant IDOR. The payout service holds its own fail-closed distributed lock (the comment insystem-jobs/runis accurate). The outbox drains are locked and bounded, org invitations are complete end-to-end, andWaitlistis the newsletter by design — event capacity has no waitlist, and a full event is simply sold out.3. The PR train (10 PRs, 4 serial waves, nothing stacked)
fix/booking-slot-integrityfix/approval-path-correctnesswithSerializableRetryon checkout, tentative-hold visibility in the conflict checkfix/refund-doctrine-and-freezefix/reschedule-cancel-lifecyclefix/org-scoping-seamfix/cron-fleet-hygieneexpire-unpaid-trialsfix, reminder windows, #1132 §08, heartbeat dead-man workflow, minimalSystemJobExecutionwrites, stalescripts/twin deletion — Part of #866, #697, #1132fix/booking-ux-money-truthperf/scheduling-freshness-and-dietfix/db-booking-constraintsdocs/booking-maintenance-refreshdocs/collaboratorsde-drift,prompts/index + seed-cohort reconciliation + refreshed booking test promptsPost-train:
fix/tz-grid-scheduling-timezonedelivers #1168. Merge order is strictly serial 1→10; the one coordinateddb push(thennpm run db:sidecars) happens after PR 9 merges and #1045 has merged.4. Decisions recorded (2026-08-13)
Heatmap freshness = client polling (~60s + focus refetch) with
s-maxageon the availability endpoints; SSE rejected on Netlify's function timeout; Supabase Realtime assessed viable-but-not-now (Broadcast-mode invalidation pings are the upgrade path once a live "seats left" surface justifies quotas, after #481). No server-action conversion — the Route Handler + useMutation convention stands. Wallet floor stays>= 0; the top-up-refund shortfall books an org receivable. #1076 ships the explain-the-bucket mitigation now, #1168 later. Approval links unify on RAZORPAY. Org context is carried end-to-end through approvals. The train-end push is additive; NOT-NULL flips and clean-data constraints stay staged for the pre-MVP reset.5. Existing-issue dispositions
#676 is superseded by this tracker (A1–A4 shipped via #847 and live-verified; B1's reconcile-by-UUID half closes via PR 3; docs drift folds into #1013/PR 10; the AE/CN/CA/RV/A11 addendum items get re-triaged after the train). #837's checkboxes are decorative — its comments are the record — and its one remaining item, the staging chaos go/no-go, merges with #874 as this train's exit gate. #309 closes as a duplicate of #997. #448 is rescoped to its audit-trail and staff-blind-spot halves and closes via PR 4. #1092 keeps the observability build-out; PR 9 carries only the drift-guard slice. #866 keeps the QStash decision with #1010 as the execution issue; PR 6 carries the heartbeat and locks. #697 keeps INF-3/4/5; INF-1 and INF-2-minimal ride PRs 3 and 6. #872 owns the DST root cause and builds on #1168. #1132 keeps its auth/SSRF/GST items; PR 6 carries §08 and wave 1 absorbs its two withheld booking/money items. #867's booking-subset items are absorbed here; the dashboard-wide remainder stays. #472, #1058, #1089, and #1128 are linked as adjacent, not carried.
6. Verification gate
Per-PR:
tsc, eslint (warnings block), targeted jest, and a background dev server exercised via mock-data API scripts. New suites: freeze-doctrine, trial-slot-integrity, accept/decline transitions,free_-rail + event-refund idempotency, org attendee-arm + list/detail parity, cron-lock registry drift, checkout price parity, and a real-service grid parity test replacing the self-mocking one. Train-wide: chaos scenarios 1/2/4 plus a new trial-vs-checkout same-minute race script after wave 1;npm run test:chaos:apiplus scenario 5 after wave 2; the coordinated push + Supabase advisors after PR 9; and the full 8-scenario staging chaos run (the #837/#874 go/no-go) — extended with freeze-during-pending-capture and freeze-then-manual-cancel scenarios — as the train exit gate.7. Deferral register (explicitly out of this train)
QStash execution (#1010); the full #997 engine port; DST (#872) and #1168 (post-train); SystemJobExecution dashboards (#1092); INF-3/4/5; no-show marking UI; guest/magic-link access; event seat-waitlist; the trial-reschedule feature (PR 7 hides the broken entry); collaborator permission enforcement (#768); the org trials dead API surface (delete-or-build pending); #906 and the cold-start cluster (#932/#937/#1117/#1120/#1124 — a separate infra train); #1132 §05 GST credit notes;
MAX_CANDIDATE_STARTS_PER_ROW; remaining UX P2 polish. #481 and #874 run as launch gates at train exit rather than as train content.PR checklist
fix/booking-slot-integrity— fix(booking): interval-granular slot locks in one namespace; trials enter the exclusion net #1170fix/approval-path-correctness— fix(booking): approval path stops inventing appointments, dropping org context, and calling gateways in-tx #1172fix/refund-doctrine-and-freeze— fix(payments): freeze obeys the cancellation doctrine; every refund rail enters one front door #1173fix/reschedule-cancel-lifecycle— fix(booking): the reschedule loop closes; lifecycle authz, proration, and audit stop lying #1174 (API); 4b UI in flightfix/org-scoping-seam— fix(dashboard): org and personal contexts stop bleeding into each other #1175 (also deletes the org-trials dead surface per the 2026-08-14 decision)fix/cron-fleet-hygienefix/booking-ux-money-truthperf/scheduling-freshness-and-dietfix/db-booking-constraints— fix(db): every documented booking guarantee becomes real, mapped to one push #1176db push+db:sidecars(after fix(db): drop LEMON_SQUEEZY and XFLOW from the live PaymentGateway enum #1045)docs/booking-maintenance-refresh— draft open at docs(booking): de-drift collaborators, document the org-funded rail, index the prompts corpus #1171 (merges LAST)Related: #1161 #1162 #1163 #1164 #1165 #1166 #1167 #1168 · #676 #837 #866 #697 #997 #1006 #1013 #1076 #1092 #1093 #1132 #448 #1085 #1010 #872 #874 #481 · Part of #1072.