Repository navigation
Gate Scheduling by plan, extend billing holds, and make HIPAA contract-only - #330
Merged
Merged
Conversation
…t-only Scheduling can't charge more than $0.10 a booking, so serious use pays through plans. Live environments only; sandbox has everything on every plan: - hosts: 3 on Free, 25 on Pro, 100 included on Scale then $0.99 a month each (a new `scheduling.hosts` meter, charged by the day like inboxes, first priced on the January 2027 card; existing live hosts wait until then) - holds on Pro and up; the embedded host portal, attribute routing and pages without "Scheduling by coss.com" on Scale and up - seats per group session, hosts per availability search, and a plan rate for live slot lists and searches What already exists keeps working; only new setup meets the plan. Billing holds and project budgets now also stop new live bookings, signature requests and inboxes, and close hosted forms. Holds already made still confirm. People booking or submitting never hear about the customer's billing. Price SMS received on Canadian numbers from January 2027: numbers are sold there but inbound was invoiced as an uncharged line. HIPAA mode needs a sales or internal contract, and is on the public site: the security page, the plan table, the pricing FAQ and the Nylas comparison, which also lists customers' own OAuth apps and booking domains as coming soon. Remove the video meter, rate and product, which never shipped. The dashboard's price list also drops meters without a price (Sign's blank row).
# Conflicts: # apps/site/src/app/(home)/pricing/page.tsx
The merged pricing tray fits at most four: SMS reminders stay in the text.
- A new host past a capped plan's allowance is refused, like a new inbox, so deleting and creating can't run past it. - The holds route checks attribute routing, like bookings and series. - A retry of a booking, series or signature request already made still answers with it during a billing hold, and sending a request already sent returns it. - The footer reads the plan in effect, not what a scheduled downgrade allows, and never shows in a sandbox.
- Plan checks never run inside a transaction: creating a host held an advisory lock while the gate waited for a second connection, which could stall the pool under concurrent creates. Updating an event type had the same shape. - A retry of a key already used meets no plan gate, for holds and API attributes. - An empty host_filter needs routing too: it matches every active host. - The host fee test no longer depends on today being before January 2027.
- gateRouting stays out of idempotency hashes, so retrying a request made before this deploys still replays it. - An expired idempotency key makes something new, so it meets the plan's gates. - A billing hold or project budget also stops new live hosts, each a new monthly charge.
Give a fee date to live hosts an older task created during a rollout, after the migration ran: without one they would never count against the limit or pay.
# Conflicts: # AGENTS.md # apps/site/content/docs/(platform)/billing.mdx # apps/site/content/docs/scheduling/pools-and-groups.mdx # apps/web/src/app/[orgSlug]/[projectSlug]/[envSlug]/scheduling/actions.ts # apps/web/src/app/[orgSlug]/[projectSlug]/[envSlug]/scheduling/hosts/page.tsx # packages/billing/src/catalog/plans.ts # packages/billing/test/engine.test.ts # packages/db/migrations/meta/0128_snapshot.json # packages/db/migrations/meta/_journal.json # packages/scheduling/src/event-types.ts # packages/scheduling/src/hosts.ts
- Slot explanations check attribute routing and the plan's slot rate, like slots. - A scheduled downgrade changes what's in use only when it applies: searches, attributes, holds and the host portal read the plan in effect. Only setup that outlives the request, such as a host filter, is checked against the plan to come.
An unchanged host filter is compared canonically: jsonb doesn't keep key order, so resending a filter that already exists could be refused as a change.
# Conflicts: # apps/site/src/lib/pricing.ts # packages/billing/AGENTS.md # packages/billing/src/catalog/entitlements.ts # packages/billing/src/catalog/plans.ts
# Conflicts: # packages/db/migrations/meta/0130_snapshot.json # packages/db/migrations/meta/_journal.json
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Nov 4, 2026
# Conflicts: # packages/db/migrations/meta/0131_snapshot.json # packages/db/migrations/meta/_journal.json
# Conflicts: # packages/db/migrations/meta/0134_snapshot.json # packages/db/migrations/meta/_journal.json # packages/scheduling/src/hosts.ts
# Conflicts: # packages/db/migrations/meta/0135_snapshot.json # packages/db/migrations/meta/_journal.json
pumfleet
enabled auto-merge
November 4, 2026 22:32
pumfleet
disabled auto-merge
November 4, 2026 22:33
# Conflicts: # apps/web/src/app/[orgSlug]/[projectSlug]/[envSlug]/scheduling/actions.ts # apps/web/src/lib/billing-preflight.ts # packages/billing/src/catalog/entitlements.ts # packages/billing/src/catalog/plans.ts
The calendar delegation test's fake gate needs hasFeature and currentLimit, and the dashboard's Scheduling actions now reach the usage gate's system organization.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follows a commercial audit of what coss.com charges for. Scheduling can't charge more than $0.10 a booking, so serious use now pays through plans.
Scheduling plan gates (live environments only; sandbox has everything on every plan):
scheduling.hostsis a new meter charged per day, like inboxes, first priced on the January 2027 card. Migration0136dates existing live hosts to 2027-01-01, and the worker'sscheduling-host-feesloop charges them from then and dates any live host created without one during a rollout.Billing holds and project budgets also stop new live bookings, Scheduling hosts, signature requests and inboxes, and close hosted forms. Holds already made still confirm, bookers and submitters never hear about the customer's billing, mail still arrives, and monthly charges continue.
Canadian inbound SMS is $0.0104 a segment from January 2027: numbers were sold there, but inbound invoiced as an uncharged line.
HIPAA is contract-only: turning it on needs a sales or internal contract, and billing still never turns it off. It's now on the security page, the plan table, the pricing FAQ and the Enterprise card. The Nylas comparison no longer says there's no BAA, and lists customers' own Google and Microsoft apps and custom booking domains as coming soon to Enterprise. Those two aren't built yet.
Video is gone: the meter, rate, product and SDK type, plus its row in the dashboard and iOS price lists. Sign's blank row is gone from those lists too.
videostays a reserved org slug.Failed-SMS fees were checked and left alone: Twilio's $0.001 applies only to messages ending
Failed, and plan send limits cap the worst case at about $10 a day on Pro and $100 on Scale.Before January 1
The Terms need 30 days' notice, so customers must be told by December 2 about the host fee, Canadian inbound SMS and the January SMS price rises. Nothing in this PR sends that notice.
Testing
bun run verify's steps on a fresh test database:biome ci(tracked directories only, since.claude/worktreesholds nested Biome configs),db:migrate:test, andturbo run typecheck test --affected, 73 of 73 tasks.apps/api/test/scheduling-plans.test.tscovers the gates, holds, replays and host fees end to end. The concurrent host-create test also passed withDATABASE_POOL_MAX=2.Review
Twelve Codex rounds. Nothing was rejected.
/holdsskipped the routing gate; retries of bookings, series and signature requests were refused during holds; the footer read a scheduled downgrade's plan and showed in sandboxes.host_filter: {}slipping past routing, and a test that would fail from January 2027./slots/explainskipped the routing gate; searches narrowed as soon as a downgrade was scheduled, which I widened to every use-time gate.mainagain (its per-person booking limit touched the same code).main(booking history and event type descriptions).mainagain (host locale increateHost).mainagain (threshold charging and credit limits).mainagain (the Scale gate on calendar delegation, kept alongside these).Noticed, not changed
createInboxhas the same shape as the round 2 P1: it calls the plan gate inside a transaction holding an advisory lock.packages/email/test/domains.test.ts:107is flaky under load: it reads one of an organization's two tenant rows with no stream filter.packages/push/test/push.test.ts:486flaked once on the order of two devices.