Skip to content

URL: https://mkdocs.justinsforge.com/memory/general/reference_creatortrack_scheduling/

CreatorTrack Scheduling Module

Status: DEPLOYED to prod 2026-07-14 (CT merge bd7ac71, 3 migrations applied to lifeos, task #39 Deployed; links-edge apex Worker live: forge merge 1652a04, wrangler 00a78508, routes reconciled, pricing/handle/app-login curls verified). Public URLs are live once a workspace claims a handle.

Plan: creatortrack-scheduling-handles-2026-07-13. Ledger: forge/comms/results/plan-ledgers/creatortrack-scheduling-handles.md.

URL model

creatortrack.ai/<handle>/<slug>; handle claimed in Settings > Profile (one active per workspace, [[reference_creatortrack_handles]]), slug picked per booking link in the Scheduling app ([ws]/scheduling). Unlisted by default: is_public is DISCOVERABILITY only (future bio-page listing); the URL always works while is_active. First unauthenticated writable surface CT has: app/(public)/[handle]/[slug] route group + /api/public/* (rate-limited, HMAC booking token minted at page render, uniform failure responses, no enumeration oracle).

Anti-double-booking guard (non-negotiable invariant)

Slots RENDER from the app.calendar_events cache (lib/scheduling/slots.ts computeSlotsPure - pure core, tz/DST-correct, buffers/min-notice/max-per-day; existing confirmed+pending bookings also block their slots). The WRITE path (lib/scheduling/book.ts bookSlot) re-verifies: (1) slot-membership vs computeSlots, (2) LIVE Google freebusy.query (verifySlotStillFree, checks busy_sources + write_target, fails NOT-free on any Google error), (3) insert app.bookings confirmed, (4) push Google event (booker as attendee), (5) store provider_event_id or flip row to failed and error out. Never confirm a booking we could not write. Idempotency: sha256(linkId|startsAt|email) UNIQUE; failed rows are revived on retry. request mode stops at pending (no Google); host approve runs the same shared write path (approveBooking), decline = cancelled + booker email.

Dead-token failure mode

checkWriteConnectionHealth (lib/google-connections.ts, 3-min in-process cache) is consulted by the slots API/page: dead refresh token = "This host's calendar is temporarily unavailable", ZERO slots offered, booking POST refuses (host_unavailable), host gets a deduped bell notification (marker persisted on the connection row's config.health). Never accept a booking we cannot write.

Integrations (all in afterBookingConfirmed, each fail-soft with logged booking id)

  • Resend emails (lib/email.ts): booker confirmation w/ .ics attachment, host notification, request-received + declined variants. Booker times formatted in BOOKER tz.
  • CRM: upsertContactFromBooking (find-or-create by email in crm.contacts.emails text[], meeting activity, bookings.contact_id link). Confirmed bookings only; no auto-deal.
  • booking.created automation event (payload carries pending flag), fired through the shared engine entry (depth/fan-out guards intact).

Gotchas

  • Rate limiter (lib/ratelimit.ts) is in-process, single-node only.
  • tsx verify scripts need NODE_PATH=/tmp/shim PGDATABASE=<db> (server-only stub; tsx does not load .env.local). Verify scripts: scripts/dev-verify-scheduling-{slots,ratelimit,book,crm,automation,deadtoken,editor,requests}.ts.
  • server-only markers were removed from ratelimit/booking-token/personal-apps/google-connections/api-connections so tsx scripts can load them.
  • Booking slugs equal to a (suite)/[ws] sub-route name are rejected (RESERVED_BOOKING_SLUGS); handles equal to any top-level app segment are reserved ([[reference_creatortrack_handles]]).