fix(audit-sprint-6a): make the Scale tier sellable - #171
Conversation
Audit deep-dive found the Scale tier was advertised in marketing for
$1,800/mo but unsellable end-to-end:
- org_subscriptions_plan_key_check rejected 'scale' (only allowed
basic|pro|enterprise)
- Onboarding plan picker excluded Scale (app/onboarding/page.tsx:54-58)
- .env.example showed STRIPE_PRICE_SCALE as commented-out optional
"comma-separated stripe price IDs" — neither true; check-env.js
already had it in productionRequiredKeys
This PR makes the path actually work end-to-end. The Stripe price ID
itself is operator-set; PR description includes the runbook.
Changes
- Migration 20260624020 drops + recreates
org_subscriptions_plan_key_check with 'scale' added. Comment on the
constraint cross-references the env var.
- app/onboarding/page.tsx: PLAN_CHOICES adds PLAN_CATALOG.scale
between pro and enterprise so new orgs see the Scale tier during
self-serve onboarding.
- .env.example moves STRIPE_PRICE_SCALE into the core STRIPE — BILLING
block (uncommented) with the correct "required in prod" note.
- __tests__/lib/plans.test.ts: expectedPlans was missing 'scale' (the
assertion silently skipped it); now correct. Adds explicit
getBillingPlan('scale') coverage with stripePriceId env resolution
+ getAllBillingPlans('scale' included) assertions.
Built on top of fix/audit-sprint-4b-plan-catalog (PR #166) because
getBillingPlan/getAllBillingPlans live there.
Validation
- tsc -p tsconfig.typecheck.json: clean
- eslint: 0 errors, 17 warnings (same as 4b base)
- jest: 5319/5334 pass (+5 from new scale tests on top of 4b's 5314)
Operator runbook for shipping Scale
1. Create the Scale product + monthly price in Stripe ($1,800 USD).
2. Add STRIPE_PRICE_SCALE=price_... to the Vercel production env.
3. Merge this PR (+ #166 first, since 6a depends on it).
4. Apply migration 20260624020 to prod.
5. Manually flip an existing test org to plan_key='scale' to verify
it doesn't bounce off the CHECK constraint.
6. Watch billing-reconcile cron (PR #162) for any 'scale' drift the
first night.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 614c1e4f0b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const PLAN_CHOICES = [ | ||
| PLAN_CATALOG.basic, | ||
| PLAN_CATALOG.pro, | ||
| PLAN_CATALOG.scale, |
There was a problem hiding this comment.
Update plan validation to accept the newly added scale tier
Adding PLAN_CATALOG.scale to onboarding choices makes users able to select Scale, but step 2 submission still calls validatePlan(planCandidate) in saveOrgDetails, and validatePlan only accepts basic|pro|enterprise (lib/validators/organization.ts PLAN_OPTIONS). In practice, choosing Scale will be rejected as invalid and redirect back with error=1, so the tier remains unsellable through onboarding despite the UI change.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR aims to make the existing Scale plan tier sellable end-to-end by aligning database constraints, onboarding plan selection, environment documentation, and plan-catalog test coverage with the already-present PLAN_CATALOG.scale and billing helpers.
Changes:
- Adds
scaleto theorg_subscriptions.plan_keyDB CHECK constraint (and documents it via a constraint comment). - Exposes Scale in the onboarding plan picker and updates
.env.exampleto documentSTRIPE_PRICE_SCALEas required in production. - Extends
lib/planstest coverage to include Scale in expected plan keys and explicitly testsgetBillingPlan('scale')+getAllBillingPlans().
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| supabase/migrations/20260624020_audit_sprint6a_scale_tier_check.sql | Expands org_subscriptions.plan_key allowlist to include scale and adds constraint documentation. |
| app/onboarding/page.tsx | Adds Scale to onboarding plan choices (but currently conflicts with existing plan validation). |
| .env.example | Documents and uncomments STRIPE_PRICE_SCALE in the core Stripe billing section; removes outdated Scale env guidance. |
| tests/lib/plans.test.ts | Adds Scale coverage for plan catalog assertions and billing helper functions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -- 2. STRIPE_PRICE_ENV['scale'] = 'STRIPE_PRICE_FOUNDATION'... wait | ||
| -- no, that's basic. Scale maps to STRIPE_PRICE_SCALE (lib/plans.ts | ||
| -- already correct). |
| -- Also tightens documentation: the existing comment on the constraint | ||
| -- (added by 20260616_org_subscriptions_plan_key_check.sql) implied the | ||
| -- catalog was basic|pro|enterprise. After this migration that's wrong. |
| // Audit Sprint 6a (2026-05-23): Scale tier added. Was deliberately | ||
| // excluded because the DB CHECK constraint on org_subscriptions | ||
| // rejected 'scale'; that's fixed by migration 20260624020. Operator | ||
| // must also set STRIPE_PRICE_SCALE in production env for checkout to | ||
| // resolve a price ID — productionRequiredKeys in check-env.js already | ||
| // guards this. | ||
| const PLAN_CHOICES = [ | ||
| PLAN_CATALOG.basic, | ||
| PLAN_CATALOG.pro, | ||
| PLAN_CATALOG.scale, | ||
| PLAN_CATALOG.enterprise, | ||
| ]; |
|
Reopened after base branch fix/audit-sprint-4b-plan-catalog was merged to main and deleted. |
Summary
Audit deep-dive found Scale was advertised in marketing at $1,800/mo but unsellable end-to-end. This PR makes the path actually work; the Stripe price ID itself is operator-set (runbook below).
What was broken
org_subscriptions_plan_key_checkconstraintbasic|pro|enterprisescaleapp/onboarding/page.tsx:54-58PLAN_CHOICESPLAN_CATALOG.scalebetween pro and enterprise.env.exampleSTRIPE_PRICE_SCALElib/plans.tsPLAN_CATALOG.scaleSTRIPE_PRICE_ENV['scale']in lib/plans.tsSTRIPE_PRICE_SCALE(added in Sprint 4b)scripts/check-env.jsproductionRequiredKeysSTRIPE_PRICE_SCALEscripts/check-stripe-prices.mjsSTRIPE_PRICE_SCALESo the scaffolding was already there — only the DB CHECK, onboarding picker, and env-example doc lagged behind.
Tests
expectedPlanswas missing'scale'(so the loop silently skipped scale). Now correct.getBillingPlan('scale')coverage includingstripePriceIdenv resolution (set → use; unset → undefined).getAllBillingPlansassertion that returns all 4 ids.Validation
npm run type-checkcleannpm run lint0 errors, 17 warnings (same as 4b base)npx jest5319/5334 pass (+5 new scale tests on top of 4b's 5314)Operator runbook for shipping Scale
STRIPE_PRICE_SCALE=price_...to Vercel production env20260624020_audit_sprint6a_scale_tier_check.sqlto prodplan_key='scale'to verify it doesn't bounce off the CHECK constraint🤖 Generated with Claude Code