feat(billing): plan taxonomy reconciliation + Stripe sync + internal-tester allowlist (#164)
All checks were successful
CI / frontend (push) Successful in 6m40s
Mirror to GitHub / mirror (push) Successful in 7s
CI / e2e (push) Successful in 10m7s
CI / backend (push) Successful in 10m34s

Co-authored-by: Michael Chihlas <michael@resolutionflow.com>
Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
This commit was merged in pull request #164.
This commit is contained in:
2026-05-11 05:07:07 +00:00
committed by chihlasm
parent dad5e1f546
commit 3f04911070
38 changed files with 745 additions and 110 deletions

View File

@@ -0,0 +1,84 @@
"""add_starter_rename_team_to_enterprise
Revision ID: 4ce3e594cb87
Revises: c6cbfc534fad
Create Date: 2026-05-07 19:36:27.172082
Plan tier taxonomy reconciliation. Marketing surface and Stripe products
named "Starter / Pro / Enterprise"; backend was on "free / pro / team".
This migration:
1. Defensively migrates any existing subscriptions on plan='team' to
plan='enterprise' (dev has zero such rows; prod is expected to have
none, but the UPDATE is safe and idempotent).
2. Renames the plan_limits row 'team' -> 'enterprise'. plan_billing
and plan_feature_defaults are FK-referenced but currently empty;
the rename works because PostgreSQL allows updating PK values when
no FK rows reference them.
3. Inserts a new plan_limits row for 'starter' between free and pro.
Resource visibility (Tree.visibility, StepLibrary.visibility) also uses
the string 'team' for "shared with my account" — that is a separate
domain and is intentionally not touched.
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '4ce3e594cb87'
down_revision: Union[str, None] = 'c6cbfc534fad'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute("UPDATE subscriptions SET plan = 'enterprise' WHERE plan = 'team'")
op.execute("UPDATE plan_limits SET plan = 'enterprise' WHERE plan = 'team'")
op.execute("""
INSERT INTO plan_limits (
plan,
max_trees,
max_sessions_per_month,
max_users,
custom_branding,
priority_support,
export_formats,
max_ai_builds_per_month,
max_ai_builds_per_24h,
kb_accelerator_enabled,
kb_max_lifetime_conversions,
kb_batch_max_size,
kb_allowed_formats,
kb_detailed_analysis,
kb_conversational_refinement,
kb_step_library_matching,
kb_history_limit
) VALUES (
'starter',
10,
75,
1,
FALSE,
FALSE,
'["markdown", "text", "html"]'::jsonb,
15,
5,
FALSE,
NULL,
NULL,
'["txt", "paste", "md"]'::jsonb,
FALSE,
FALSE,
FALSE,
NULL
)
ON CONFLICT (plan) DO NOTHING
""")
def downgrade() -> None:
op.execute("DELETE FROM plan_limits WHERE plan = 'starter'")
op.execute("UPDATE plan_limits SET plan = 'team' WHERE plan = 'enterprise'")
op.execute("UPDATE subscriptions SET plan = 'team' WHERE plan = 'enterprise'")