Skip to content

fix(booking): weekly availability rows mean the consultant's local day everywhere, and day segments are half-open (#1343, #1342, #1326, #1348, #1415, #1416) - #1512

Merged
teetangh merged 4 commits into
devfrom
fix/availability-day-semantics-and-boundaries
Sep 5, 2026
Merged

fix(booking): weekly availability rows mean the consultant's local day everywhere, and day segments are half-open (#1343, #1342, #1326, #1348, #1415, #1416)#1512
teetangh merged 4 commits into
devfrom
fix/availability-day-semantics-and-boundaries

Conversation

@teetangh

@teetangh teetangh commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

The one rule this PR establishes: SlotOfAvailabilityWeekly.startDay is the day the consultant published, in their own local calendar, and the UTC weekday is always derived from it through the row's own frozen utcOffsetMinutes. Four surfaces had each answered that question for themselves, and the disagreements are the six issues below.

Issue What was wrong Fix
#1343 The settings save path shifted the day forward or back to the UTC day the converted instant landed on, while onboarding stored the local day. An Asia/Kolkata row starting before 05:30 local walked back one weekday on every save — Monday 01:00 saved as Sunday, reloaded into Sunday's form row, saved again as Saturday. shiftDayOfWeek and the startDayOffset block are gone. utils/schedule/formatting.ts now has one builder, weeklySlotForSave, that both save paths use; formatWeeklySlot is a thin adapter over it and keeps the #1125 all-or-nothing throw contract.
#1342 The calendar grid bucketed rows on the viewer's weekday and did not even select utcOffsetMinutes, so an overseas customer was shown an IST pre-05:30 row a day away from the day checkout would accept. New utils/schedule/weekly-projection.ts owns utcStartDayIndex, weeklyRowDurationMinutes and weeklyRowOccurrencesInRange — the single generator the grid, isMinuteWithinWeeklySlot, getNextOccurrenceWeekly and matchWeeklySlotToDay all share. The grid endpoint carries the stored columns through instead of flattening them onto a 1970 reference date, and processWeeklySlots loses its timezone parameter because display zoning belongs downstream in splitSlotsByDay.
#1326, #1348 item 1 Onboarding and the per-row PATCH defaulted a consultant with no profile timezone to UTC 0 rather than to the launch offset, and nothing checked a caller who supplied an offset of their own. New lib/scheduling/weeklyUtcOffset.ts is the only answer: derived from User.timezone, 330 when the profile carries no usable zone (ADR 17), WeeklyOffsetConflictError → 400 UTC_OFFSET_CONFLICT when a caller contradicts it, and one Sentry warning per write (never per row) when the consultant publishes from outside Asia/Kolkata. PIN_TO_LAUNCH_OFFSET is the one-line switch to hard-pin every row to IST. Onboarding also stamped the offset after mergeAdjacentWeeklyRows, so that fold's cross-offset guard was comparing undefined with undefined; it is stamped before the fold now.
#1415 splitSlotsByDay cut day segments at endOfDay (23:59:59.999). A 23:30–23:59:59.999 remainder is not a thirty-minute atom, so a block published up to local midnight silently lost its last bookable slot on every surface. Segments are half-open: a segment ends at the next local day's midnight.
#1416 The expert page's display merge carried a 60-second tolerance where booking requires exact adjacency, so the availability card advertised a window whose seam no row publishes and checkout's per-atom union coverage then refused the booking. mergeConsecutiveSlotsForDisplay requires exact adjacency, and its docblock now states the truth: the only difference from mergeConsecutiveSlots is which atoms are eligible (same-status versus available-only).

Per user decision 6 / #872, all four weekly write paths additionally dual-write the five DST columns (timezone, localStartMinutes, localEndMinutes, localStartDay, localEndDay) from the same resolver, computed after the merge so they describe the row actually stored. Nothing reads them. coalesceConsultantWeeklyRows deletes and recreates rows, so it recomputes them for the merged row rather than losing them on the next coalesce. The schema.prisma doc-comment changes from "unwritten" to "written from 2026-09-05, read by nothing until the reader flip" — comment only, no schema change, no db push needed.

No data repair. The rows the old settings path wrote are pre-MVP mock data; the no-backfill rule applies and the pre-MVP reset wipes them.

Rebands worth recording

Files touched

New: utils/schedule/weekly-projection.ts, lib/scheduling/weeklyUtcOffset.ts, __tests__/booking-algorithm/weekly-day-semantics.test.ts.

Changed: utils/schedule/formatting.ts, utils/timeSlotsProcessing.ts, utils/slotAllocation/slotTimeUtils.ts, utils/slotAllocation/SlotAllocationService.ts, utils/slotAllocation/mergeAdjacentWeeklyRows.ts, utils/onboarding-server.ts, app/api/slots/availability-with-allocation/[consultantId]/route.ts, app/api/slots/availability/weekly/route.ts, app/api/slots/availability/weekly/[id]/route.ts, app/api/user/consultants/[id]/route.ts, app/explore/experts/[consultantId]/utils/mergeSlots.ts, prisma/schema.prisma (comment only), __tests__/schedule/format-slots-for-api-throws.test.ts.

Docs: docs/booking/00-architecture-decisions.md (ADR B4 rewritten, register row and status updated), docs/booking/02-event-types-and-validation.md, docs/booking/03-slot-math-and-calculations.md (new section "Projecting a weekly row onto real dates"), docs/booking/19-dst-and-timezone-posture.md (rules 1–4 corrected — the local columns are written now and the drift warning exists), .claude/skills/booking/references/availability.md §1/§2/§4. docs/booking/05-troubleshooting-and-changelog.md is deliberately untouched; PR-D writes one consolidated section.

Verification

Check Command Result
Types rm tsconfig.tsbuildinfo && NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit Clean, zero diagnostics (run twice: before and after the Prettier pass). npx prisma generate deliberately not run — a sibling worktree owns the shared client.
Lint npx eslint over all 15 changed/new TypeScript files 0 errors, 2 warnings, both pre-existing no-explicit-any at SlotAllocationService.ts:3445 and :3498 (verified identical on origin/dev at lines 3446/3499, only shifted by edits above them). Nothing new introduced.
Format npx prettier --check over all 21 changed files All matched files use Prettier code style.
Tests npx jest __tests__/booking-algorithm __tests__/booking __tests__/schedule 84 suites passed, 1294 tests passed, exit 0.
A12.1 grid ⇔ validator walk Throwaway script, not committed: every 30-minute atom of the week 2026-09-06 → 2026-09-13 for the IST Monday 01:00–05:00 row ({MONDAY, 1170–1410, offset 330}), asserting weeklyRowOccurrencesInRange coverage ⇔ isMinuteWithinWeeklySlot. ATOMS=336 GRID=8 VALIDATOR=8 MISMATCHES=0 — the eight atoms are the four hours the row publishes, and the grid and the validator agree on every one of the 336.

The committed pin, __tests__/booking-algorithm/weekly-day-semantics.test.ts, is table-driven, node-environment and Prisma-free, and covers all six required areas: the two save paths agreeing on startDay (including re-save idempotency), the offset resolver's table plus a source assertion that the four write paths call resolveWeeklyUtcOffsetMinutes and no longer call getTimezoneOffsetMinutes directly, grid-equals-validator for the IST pre-dawn row seen from both an Asia/Kolkata and an America/New_York viewer, the midnight block keeping its 23:30 window, display-merge-equals-booking-merge over gaps of 0/1,000/60,000 ms, and the dual-written local columns. slotTimeUtils.test.ts, availability-window-merge.test.ts, availability-grid-conditional-get.test.ts, availability-window-scan.test.ts and slot-session-fix-pins.test.ts were run and are green, and were not edited.

Not done / follow-ups

Closes #1343
Closes #1342
Closes #1326
Closes #1348
Closes #1415
Closes #1416
Part of #872
Part of #1433

🤖 Generated with Claude Code

https://claude.ai/code/session_01EyngsXG829TRTBof4CSGT1

teetangh and others added 2 commits September 5, 2026 22:34
…y everywhere, and day segments are half-open (#1343, #1342, #1326, #1348, #1415, #1416)

`SlotOfAvailabilityWeekly.startDay` had four readings and no owner. The settings
save path shifted it to the UTC day the converted instant landed on, onboarding
stored the consultant's local day, the validator and the allocator assumed the
local day, and the calendar grid matched rows against the VIEWER's weekday. For
an Asia/Kolkata consultant every row starting before 05:30 local therefore
walked back one weekday on each save — Monday 01:00 saved as Sunday, reloaded
into Sunday's form row, saved again as Saturday — and an overseas customer was
shown those rows a day away from the day checkout would accept a booking on.

This establishes one rule: `startDay` is the day the consultant published, in
their own calendar, and the UTC weekday is always derived from it through the
row's own frozen `utcOffsetMinutes`. That derivation lived in three copies and
was missing from the grid entirely; it now lives once in
`utils/schedule/weekly-projection.ts`, which also owns the overnight-aware
duration and `weeklyRowOccurrencesInRange`, the single generator the grid, the
allocator's next-occurrence and day-match helpers, and the pin all share. The
two save-shape builders in `utils/schedule/formatting.ts` collapse into one, so
they cannot disagree again.

The offset itself was equally unowned: onboarding and the per-row PATCH
defaulted a consultant with no profile timezone to UTC 0 rather than to the
launch offset, and nothing checked a caller who sent an offset of their own.
`lib/scheduling/weeklyUtcOffset.ts` is now the only answer — derived from
`User.timezone`, 330 when the profile has no usable zone (ADR 17), a 400
carrying `UTC_OFFSET_CONFLICT` when a caller contradicts it, and one Sentry
warning per write when the consultant publishes from outside Asia/Kolkata,
which is the signal that the #872 reader work has become due. All four weekly
write paths now also dual-write the five DST columns from the same resolver;
nothing reads them, and the coalesce pass that deletes and recreates rows
carries them across so they are not silently unwritten. Onboarding additionally
stamped the offset after `mergeAdjacentWeeklyRows` rather than before it, so
that fold's cross-offset guard was comparing undefined with undefined.

Two boundary bugs fall out of the same area. `splitSlotsByDay` cut day segments
at 23:59:59.999 instead of the next day's midnight, and a 23:30–23:59:59.999
remainder is not a thirty-minute atom, so a consultant who published up to local
midnight lost their last bookable slot on every surface. The expert page's
display merge carried a 60-second tolerance where booking requires exact
adjacency, so the availability card advertised windows whose seam no row
publishes and checkout's per-atom union coverage then refused them.

No data repair: the rows the old settings path wrote are pre-MVP mock data and
the reset wipes them, per the no-backfill rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyngsXG829TRTBof4CSGT1
…n and the DST dual-write

ADR B4 was written as a storage decision and left the meaning of the day enum
unstated, which is how four surfaces came to disagree about it; it now states
the rule, the history that produced it and the alternative that was rejected,
and its status changes from wholly superseded to live with only its grouping
half superseded by B9. `03-slot-math-and-calculations.md` gains a section on
projecting a weekly row onto real dates that carries the formula, the shared
generator, the half-open segmentation rule and the exact-adjacency merge rule.
`02-event-types-and-validation.md` says LOCAL where it used to say only "source
of truth", and names the helper. `19-dst-and-timezone-posture.md` is corrected
where it now contradicted the code: the local columns are dual-written rather
than unwritten, the drift warning it asked for exists, and rule 1 becomes "never
read them, and carry them through any path that recreates rows". The booking
skill's availability reference gets the same three facts, because it is what a
future agent loads before touching this code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyngsXG829TRTBof4CSGT1
@netlify

netlify Bot commented Sep 5, 2026

Copy link
Copy Markdown

Deploy Preview for familiarise ready!

Name Link
🔨 Latest commit 23a6126
🔍 Latest deploy log https://app.netlify.com/projects/familiarise/deploys/6a9c57c15a8dd900072fa20b
😎 Deploy Preview https://deploy-preview-1512--familiarise.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 38 (🔴 down 15 from production)
Accessibility: 90 (no change from production)
Best Practices: 83 (no change from production)
SEO: 90 (🟢 up 8 from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 89 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: c3eea58b-9b0f-43c9-a666-08f98391430f

📥 Commits

Reviewing files that changed from the base of the PR and between 73c0d9f and 23a6126.

📒 Files selected for processing (21)
  • .claude/skills/booking/references/availability.md
  • __tests__/booking-algorithm/weekly-day-semantics.test.ts
  • __tests__/schedule/format-slots-for-api-throws.test.ts
  • app/api/slots/availability-with-allocation/[consultantId]/route.ts
  • app/api/slots/availability/weekly/[id]/route.ts
  • app/api/slots/availability/weekly/route.ts
  • app/api/user/consultants/[id]/route.ts
  • app/explore/experts/[consultantId]/utils/mergeSlots.ts
  • docs/booking/00-architecture-decisions.md
  • docs/booking/02-event-types-and-validation.md
  • docs/booking/03-slot-math-and-calculations.md
  • docs/booking/19-dst-and-timezone-posture.md
  • lib/scheduling/weeklyUtcOffset.ts
  • prisma/schema.prisma
  • utils/onboarding-server.ts
  • utils/schedule/formatting.ts
  • utils/schedule/weekly-projection.ts
  • utils/slotAllocation/SlotAllocationService.ts
  • utils/slotAllocation/mergeAdjacentWeeklyRows.ts
  • utils/slotAllocation/slotTimeUtils.ts
  • utils/timeSlotsProcessing.ts

Comment @coderabbitai help to get the list of available commands.

@teetangh
teetangh marked this pull request as ready for review September 5, 2026 17:08
@teetangh

teetangh commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your current included review allowance is based on your included PR review attempts over the past 7 days. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 53 minutes.

@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

@teetangh
teetangh merged commit 01a3773 into dev Sep 5, 2026
8 checks passed
teetangh added a commit that referenced this pull request Sep 5, 2026
…nance, no-show cancels write history, and the doctrine text matches the sweeps (#1506) (#1516)

## Summary

1. **`expire-stale-requests` joins `FINANCIAL_JOB_NAMES`.** Its `expirePaymentPendingRequests`/`expireApprovedUnallocatedSubscriptions` passes call `refundPaymentsForExpired`, a refund front-door caller like every other job already in the set, so DEGRADED maintenance now holds it with the rest. `detect-consultant-no-shows` was already there via #1505.
2. **A registry pin gates every future refund-front-door caller, not just today's two.** `__tests__/maintenance/cron-lock-registry.test.ts` gains one assertion that greps `scripts/**/*.ts` for callers of `refundBookingPayment(`, `refundWholeEventPayments(`, `refundRemovedAttendeeSeat(`, and `refundPaymentsForExpired(`, and asserts each caller's `withCronLock` name is in `FINANCIAL_JOB_NAMES`.
3. **Two more money-twin routes drop their `status: () => 200` override.** `app/api/cleanup/process-payouts/route.ts` and `.../sweep-abandoned-overage-charges/route.ts` now fall through to `cleanup-route.ts`'s default `statusFor`, which reads `result.success`, mirroring `release-earnings`/`sync-payment-earnings` under #1390.
4. **The no-show cancel writes a `BookingStatusHistory` row.** `claimConsultantNoShow` in `scripts/appointments/detect-consultant-no-shows.ts` now runs the CANCELLED transition through `transitionConsultationRequest` inside `prisma.$transaction`, instead of a bare `consultation.updateMany`. The zero-row "someone else moved it" outcome is preserved via `IllegalTransitionError` catch. Candidate query, grace/handoff constants, refund call and notifications are untouched.
5. **Doctrine text corrected in `.claude/skills/booking/SKILL.md`.** Rule 2 no longer claims `expire-stale-requests.ts`/`cleanup-tentative-slots.ts` hard-delete tentative holds (fixed by #1380/#1424's soft-cancel via `transitionSlotCompletion`). Rule 5 no longer names `expirePaymentPendingRequests` as the doctrine's counter-example (fixed by #1423's CAS + money-predicate rewrite).
6. **`docs/booking/18-state-machines.md`'s reschedule section corrected.** `COUNTERED` is noted as an unreachable enum edge with no writer (the counter-round was removed per `lib/booking/reschedule-proposals.ts`), and `AUTO_ACCEPTED` is documented as the second terminal-acceptance state.
7. **Glossary linked.** `docs/booking/README.md` links `docs/enterprise/00-foundations/07-slots-sessions-glossary.md` under Core Concepts.
8. **DEGRADED gate noted in both cron references.** `docs/booking/13-cron-jobs-and-background-tasks.md`'s Safety paragraphs and `docs/maintenance/04-cron-jobs-reference.md`'s table rows for both jobs now say they are held during DEGRADED as well as OFFLINE.
9. **Consolidated train changelog.** One new `## Changelog: 2026-09-05 — booking closure train` section in `docs/booking/05-troubleshooting-and-changelog.md`, with one subsection per train PR (#1512, #1513, #1514, #1515, this PR), written from each PR's merged/open body. Also fixes the stale "only surviving `MANUAL_REVIEW` path" sentence that #1513 obsoletes.

## Files

- `lib/maintenance-cron.ts`
- `app/api/cleanup/process-payouts/route.ts`
- `app/api/cleanup/sweep-abandoned-overage-charges/route.ts`
- `scripts/appointments/detect-consultant-no-shows.ts`
- `__tests__/maintenance/cron-lock-registry.test.ts`
- `__tests__/booking/no-show-refund-front-door.test.ts`
- `__tests__/maintenance/no-show-auto-complete-handoff.test.ts`
- `.claude/skills/booking/SKILL.md`
- `docs/booking/18-state-machines.md`
- `docs/booking/README.md`
- `docs/booking/13-cron-jobs-and-background-tasks.md`
- `docs/maintenance/04-cron-jobs-reference.md`
- `docs/booking/05-troubleshooting-and-changelog.md`

## Verification

| Check | Result |
| --- | --- |
| `rm tsconfig.tsbuildinfo && NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit` (after rebase onto `01a377342`) | exit 0, no errors |
| `npx eslint` on all 7 changed/new code files | 0 problems |
| `npx prettier --check` on all 7 changed/new code files | clean |
| `npx prettier --check` on the 6 changed docs files | 5 clean; `docs/booking/18-state-machines.md` was already Prettier-dirty on `origin/dev` (unpadded tables and a wrapped bullet outside the section I touched) and is left as-is per the existing project pattern (see #1514's note on the same posture); the lines I added are themselves Prettier-clean |
| `npx jest __tests__/maintenance __tests__/booking __tests__/appointments` (after rebase) | exit 0 — **87 suites, 1348 tests passed** |
| Two suites mocked Prisma without `$transaction` (`__tests__/booking/no-show-refund-front-door.test.ts`, `__tests__/maintenance/no-show-auto-complete-handoff.test.ts`) | extended the mocks with `$transaction`, `consultation.findUnique`, and `bookingStatusHistory.create` rather than weakening any assertion |

## Not done

- None of the six numbered spec items were skipped.

Closes #1506
Part of #1338
Part of #1493
Part of #1420

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EyngsXG829TRTBof4CSGT1
@teetangh
teetangh deleted the fix/availability-day-semantics-and-boundaries branch September 5, 2026 23:42
@teetangh

teetangh commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Sentry finding from the branch deploy (poller, 2026-09-05 23:45Z, release 363e074, consultant profile e7f961b1…, trace 0a33363d): saving weekly availability fails for a consultant whose profile timezone is not Asia/Kolkata (this one is Africa/Mogadishu, +180).

  • Server: resolveWeeklyUtcOffsetMinutes (lib/scheduling/weeklyUtcOffset.ts ~L96-114) resolves the row offset to the launch value (330) and throws WeeklyOffsetConflictError → 400 UTC_OFFSET_CONFLICT because the client supplied +180, computed from the browser's own zone in formatSlotsForApi.
  • Client: SettingsTab.tsx (~L485-486) only checks response.ok and throws a bodiless Error("Failed to update settings"), so the UTC_OFFSET_CONFLICT reason never reaches the consultant (FAMILIARISE_WEB-2T; 2S is a Failed to fetch on the retry). FAMILIARISE_WEB-2R is the intentional once-per-write warning from the same resolver and was resolved as expected noise.

Net effect on this branch: any non-IST consultant cannot save weekly availability and gets no actionable message. Two ways to close it before merge: have the client derive the offset from the server-pinned value (or omit utcOffsetMinutes and let the resolver stamp it) rather than from the browser zone, and/or surface the UTC_OFFSET_CONFLICT message from the response body in SettingsTab. Left to this PR since it owns both files.

teetangh added a commit that referenced this pull request Sep 6, 2026
…1523)

## Summary

Release of the 2026-09-05/06 booking backlog closure train plus the fixes found by the end-to-end pass on the dev deploy. Base: prod at 576b97c; head: dev at 9b45a82.

## Schema

Additive only, already pushed to the shared database on 2026-09-06 with sidecars applied and asserted (45 constraints / 6 indexes / 3 triggers): `CancellationPolicy`, `CancellationPolicyTier`, `Appointment.cancellationPolicyId` (+ index), enum `CancellationPolicyStatus`. The platform default policy row is provisioned. Backup: `familiarise-pre-schema-push-20260906-0423.dump`. No push is needed for this release.

## Commits (dev not in prod)

- 9b45a82 fix(booking): allocation-lock deserialization, reschedule auto-confirm diagnostics, Razorpay CSP, planner class dates (#1520)
- ea46cd9 fix(booking): the initial-allocation advisory lock runs through $executeRaw so nothing deserialises a void column (#1518) (#1519)
- 363e074 chore(booking): the two refunding sweeps are gated in DEGRADED maintenance, no-show cancels write history, and the doctrine text matches the sweeps (#1506) (#1516)
- eee250e feat(booking): cancellation terms are typed versioned rows with per-org tiers, and a credit-funded partial cancel restores the credit in full (#1499, #1500, #1372) (#1513)
- a800874 fix(booking): the allocator no longer declines the reschedule proposal it is confirming, and accept serialises on the appointment lock (#1340) (#1515)
- 7a0bf0a fix(booking): the cancel and reschedule routes move status through the CAS helpers, and cancelled slots are tombstoned (#1383)
- 01a3773 fix(booking): weekly availability rows mean the consultant's local day everywhere, and day segments are half-open (#1343, #1342, #1326, #1348, #1415, #1416) (#1512)
- 73c0d9f fix(dashboard): the consultant Home badge counts personal requests like the card below it, class cards show their first session, and a trial's Pay Now lands on the branded checkout (#1345, #1346, #1429) (#1514)
- a0d6c09 fix(booking): auto-complete hands an unattended consultation to the no-show detector instead of closing it first (#1504) (#1505)
- a7c36ec fix(content): honest public stats, and a plan title instead of a UUID in the payment-success notification (#1484, #1485) (#1489)
- fe0c476 chore(skills,agents): reorganise Claude Code skills and agents into a domain hierarchy (#1483)
- b760943 chore(ci): make Claude review/mention workflows on-demand, not a red check (#1480)
- 9dc665a feat(plans): sole-owner archive/restore for consultation, subscription, webinar and class plans (#1507)

## Verification

- Every PR merged green (Lint, TypeScript/Tests/Build, SonarCloud gate OK, CodeRabbit threads triaged and resolved).
- End-to-end on the dev branch deploy (Chrome DevTools + Supabase MCP): availability day anchoring, Home badge parity, org tier editor incl. negative cases, org-funded checkout stamping the org policy version and refunding 100% on the INTERNAL rail, personal booking stamping the platform row, DEGRADED-gate probe 401, no console errors or 5xx.
- Post-fix re-verification on the same deploy (9b45a82): planner class cards show the first session (payload `firstSessionAt` 2026-09-10T10:00Z, card 3:30 PM IST); a consultee proposal on a green slot auto-confirms (`autoConfirmed: true`, request `AUTO_ACCEPTED`, slots moved, history rows PENDING → RESCHEDULED ×2 → APPROVED → AUTO_ACCEPTED).

## Owner items unchanged by this release

- Novu: production uses the Development environment key and the account's workflow limit blocks creating the two missing workflows (#1511).
- Resend key (#1298), Razorpay LIVE keys (#1377), load gate (#874).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EyngsXG829TRTBof4CSGT1
@teetangh

teetangh commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Correction to my comment above: the 400 UTC_OFFSET_CONFLICT mechanism is not supported by the evidence — the settings page never sends utcOffsetMinutes, and the sampled server span in trace 0a33363d answered 200 with only the intentional non-IST warning. The real, verifiable defect (the client discards the error body) is now tracked as #1526.

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