feat: self-serve signup backend (Phase 1) #161
Reference in New Issue
Block a user
Delete Branch "feat/self-serve-signup-spec"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Backend foundation for public self-serve signup. Reuses the existing
subscriptions/plan_limits/feature_flags/account_invites/email_verification_tokensinfrastructure and adds four small tables (oauth_identities,plan_billing,sales_leads,stripe_events) plus columns onusers/accounts/account_invites. Replaces the trial auto-downgrade indeps.pywith two non-mutating dependencies, adds a realBillingServicewrapping Stripe, extends the existing webhook stub, wires Google + Microsoft OAuth callbacks, makes register auto-send the verification email, and adds bulk-create + soft-revoke invite routes.Spec:
docs/superpowers/specs/2026-05-05-self-serve-signup-onboarding-design.mdPlan:
docs/superpowers/plans/2026-05-06-self-serve-signup-phase-1-backend.mdWhat's in the diff
c6cbfc534fad.Subscription.has_pro_entitlement+complimentarystatus; trial auto-downgrade indeps.pyremoved;require_active_subscription+require_verified_email_after_gracemounted on Pro routers (trees, sessions, scripts, FlowPilot, etc.).BillingService.start_trialwired into/auth/register;/billing/checkout-session+ idempotentapply_subscription_event(writes throughstripe_eventsfor replay safety); webhook handler dispatches to BillingService.oauth_identitieslinking; OAuth-only login + password-reset guards (returnuse_oauth_providerwith linked providers list, anti-enumeration on/password/forgot).EmailService.send_account_invite_emailintoPOST /accounts/me/invites; newPOST /me/invites/bulkandDELETE /me/invites/{id}(soft-revoke, idempotent).GET /billing/stateaggregates Subscription + PlanLimits + PlanBilling + resolved feature flags.c6cbfc534fadmigration sets all live accounts to complimentary Pro; preservescanceled/past_due.Test plan
pytest --override-ini="addopts="→ 1167 passed (35 deselected). Up from 1116 baseline = 50 net-new tests across Phase 1.alembic heads→ single headc6cbfc534fad.STRIPE_SECRET_KEY; webhook receiver no-ops withoutSTRIPE_WEBHOOK_SECRET.SELF_SERVE_ENABLEDflag added (defaultsfalse) for Phase 2 to consume.c6cbfc534fadonce on prod (forward-only; no data loss; preserves canceled/past_due rows).Conftest / test changes worth flagging
test_userfixture now seeds a Pro/activeSubscriptionafter registration so Pro-guarded routers don't 402 every authenticated test. Same fortest_admin.test_subscription_limits.py(the two free-plan tests downgrade inline) andtest_kb_accelerator.py::TestQuota::test_get_quota(thekb_setupfixture downgrades to free). Behavior unchanged from the user's perspective; tests just declare their plan explicitly.Followups deferred (logged in
.ai/HANDOFF.md)_store_refresh_token. Refresh tokens issued by Google/Microsoft callbacks aren't persisted torefresh_tokens. Decide before Phase 2 dark-launch whether to extract_store_refresh_tokento a shared module and call it from_sign_in_or_register.stripe_enabledwas relaxed tobool(STRIPE_SECRET_KEY)(down from also requiringSTRIPE_WEBHOOK_SECRET). The webhook handler independently checks the webhook secret before callingconstruct_event, so signature verification is still safe — but audit other callers if any rely on the old shape.backend/app/core/stripe_handlers.pyis a stub module no longer referenced after Task 16. Safe to delete in a follow-up.🤖 Generated with Claude Code