Files
orca/cloud/apps/billing/migrations/0005_trials.sql
T
Neil 51ace4d05f feat(billing): one-time Orca Cloud trial (N11) and its desktop surfaces (T1)
Billing Worker:
- trials + trial_claims (migration 0005): 2 sandbox hours per new team, no card, no Stripe.
- POST /v1/billing/trial grants it (idempotent, any member); grantTrial/trialOffer exported for
  the setup session. offers.trial on GET entitlements only when this caller could still claim it.
- Abuse limits: once per team, Orca user, normalized verified email, GitHub user (token claim)
  and GitHub owner (POST /v1/internal/trial/github from the binding; a reused identity revokes),
  disposable-domain blocklist, 3 grants per IP per day. Keys stored as SHA-256 hashes.
- Entitlement trial {includedSeconds, usedSeconds, remainingSeconds, exhausted}; metered from
  usage_events, concurrency 1, never overage; trial_used_up (suspendRunning) / trial_unavailable.
- Any subscription ends the trial; unused trial hours lapse.
- e2e: trial path (grant, no Stripe customer, concurrency, metering to used up, once per user,
  plan ends it) with cleanup of the run's abuse keys.

Desktop: trial_used_up / trial_unavailable classify as 'plan'; copy in billing, phone and CLI;
"Trial" badge, TrialPlanSummary in Plan & billing and the plans page strip, account menu row
"Trial · {{count}} sandbox hours left" (Choose plan for owners/admins); billingClaimTrial IPC
and claimOrcaCloudTrial() for the composer.
2026-11-10 06:20:22 -08:00

32 lines
1.4 KiB
SQL

-- One-time trial (N11, O7): 2 sandbox hours per new team, no card, granted once per team and
-- once per user, verified email, GitHub user and GitHub owner. A subscription on the account ends
-- it (entitlements.ts ignores the trial once any subscription row exists). Times are epoch ms.
CREATE TABLE trials (
account_id TEXT PRIMARY KEY,
granted_at INTEGER NOT NULL,
included_seconds INTEGER NOT NULL DEFAULT 7200,
-- Cache of SUM(usage_events.weighted_seconds) with no subscription (period_start IS NULL).
used_seconds INTEGER NOT NULL DEFAULT 0,
exhausted_at INTEGER,
granted_by_user TEXT NOT NULL,
github_owner_id TEXT,
github_user_id TEXT,
-- Set when a later check (GitHub binding) finds the same GitHub user/owner already used a trial.
revoked_at INTEGER,
revoked_reason TEXT
);
-- Abuse keys. kind: 'user' | 'email' | 'github_user' | 'github_owner' | 'ip'. key_hash is SHA-256
-- of the normalized value, so raw emails and IPs are never stored. granted = 1 once the key is
-- consumed by a granted trial (0 = seen on the account before a grant, e.g. a GitHub binding).
CREATE TABLE trial_claims (
kind TEXT NOT NULL,
key_hash TEXT NOT NULL,
account_id TEXT NOT NULL,
granted INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
PRIMARY KEY (kind, key_hash, account_id)
);
CREATE INDEX trial_claims_account ON trial_claims(account_id);
CREATE INDEX trial_claims_recent ON trial_claims(kind, key_hash, created_at);