Files
resolutionflow/.ai/HANDOFF.md
Michael Chihlas 3f04911070
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
feat(billing): plan taxonomy reconciliation + Stripe sync + internal-tester allowlist (#164)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com>
Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
2026-05-11 05:07:07 +00:00

7.1 KiB
Raw Blame History

HANDOFF.md

Last updated: 2026-05-08

Active task: PR #164 (feat/billing-plan-taxonomy) open in Gitea with 5 commits at head 2c9f5e9. Closes the last code blockers for self-serve cutover. After merge, only manual ops remain (Stripe live-mode Dashboard config, Railway prod env vars, internal validation, flag flip). PR #162 and #163 merged into main this session as squash commits f1be3ab and dad5e1f.

Where this session ended

PR #164 commits (oldest → newest):

  1. ba36c47 feat(billing): reconcile plan taxonomy and add Stripe sync script — migration 4ce3e594cb87 renames plan_limits.plan='team''enterprise' (defensive update of any subscriptions on the old slug; dev had zero), adds starter row with caps interpolated between free and pro. Code rename across schemas, Subscription paid-plan checks, admin endpoints, frontend useSubscription. Resource visibility (Tree.visibility='team', StepLibrary.visibility='team') is a separate domain and intentionally untouched. New backend/scripts/sync_stripe_plan_ids.py — idempotent upsert of plan_billing rows from Stripe products by exact name match, picks active monthly recurring price, leaves annual fields NULL by design.
  2. a628b24 chore(dev): pass STRIPE_* env to backend container — wires STRIPE_* + SELF_SERVE_ENABLED + INTERNAL_TESTER_EMAILS through docker-compose.dev.yml. New repo-root .env.example.
  3. 8494366 feat(billing): add INTERNAL_TESTER_EMAILS allowlist for self-serve soft cutoverSettings.is_internal_tester + is_self_serve_active_for, new get_current_user_optional dep, /config/public honors allowlist for authenticated callers, /auth/register allows allowlisted emails without invite code. 5 regression tests in test_config_public.py.
  4. 8649a4a docs: refresh CURRENT-STATE, ROADMAP, README, DECISIONS for self-serve cutover — CURRENT-STATE bumped with PR #159164 entries; ROADMAP got a "Status as of 2026-05-07" preamble (historical content preserved underneath); README fixed legacy patherly_postgres and UI-DESIGN-SYSTEM.md references; DECISIONS appended two entries.
  5. 2c9f5e9 fix(frontend): page-title — escapes + propagate plan taxonomy through frontend typesLandingPage.tsx had (six literal characters) inside JSX attribute strings, rendering as literal text in browser tabs. Replaced with literal em dash. PageMeta default tagline updated from "Decision Tree Platform" (stale) to "AI-Powered Troubleshooting for MSPs". Fixed TS errors that surfaced from the previous taxonomy commit not propagating through frontend types — types/{account,admin,billing}.ts, admin/{AccountsPage,InviteCodesPage}.tsx, AccountSettingsPage.tsx, subscription/CheckoutButton.tsx. tsc -b clean, lint clean.

Stripe state (test mode via MCP, livemode=false): 3 active products (Starter $19.99/mo, Pro $29.99/mo, Enterprise no price); leftover Enterprise $500/mo test price archived (had to clear default_price on the product first); plan_billing populated for all three tiers in dev DB via sync_stripe_plan_ids.py.

Working tree clean (only pre-existing untracked files: abc-feat-self-serve-signup-phase-2-design-...md, core.*, docs/architecture/, docs/tutorials/ — same set noted in prior handoff as "do not stage").

Single alembic head: 4ce3e594cb87 after PR #164 merges (was c6cbfc534fad).

Resume point

  1. Verify PR #164 CI green: curl -fsSL https://gitea.resolutionflow.com/api/v1/repos/chihlasm/resolutionflow/commits/2c9f5e9/status | python -m json.tool
  2. Squash-merge PR #164.
  3. Phase O manual ops (after merge):
    • Stripe Dashboard live-mode: 3 Products, monthly Prices for Starter ($19.99) + Pro ($29.99), no Prices on Enterprise (sales-led), Customer Portal with plan-switching disabled, webhook at https://api.resolutionflow.com/api/v1/webhooks/stripe with 5 events. Save live signing secret.
    • Railway prod env: STRIPE_SECRET_KEY=sk_live_..., STRIPE_WEBHOOK_SECRET, STRIPE_PUBLISHABLE_KEY + VITE_STRIPE_PUBLISHABLE_KEY (frontend redeploy required — Vite bake-at-build, Lesson 60), OAUTH_REDIRECT_BASE=https://resolutionflow.com, SELF_SERVE_ENABLED=false (still false at this point), INTERNAL_TESTER_EMAILS=<allowlist>, prod Google + Microsoft OAuth credentials.
    • Run sync against prod backend: railway run python -m scripts.sync_stripe_plan_ids. Verify plan_billing rows have sk_live_* price IDs.
  4. Internal validation (Phase O Task 46): 9 scenarios with internal testers whose emails match INTERNAL_TESTER_EMAILS.
  5. Flag flip (Task 47): email pilots, set SELF_SERVE_ENABLED=true + VITE_SELF_SERVE_ENABLED=true (frontend redeploy). PostHog signup-funnel dashboard + Sentry alert at >1/hour Stripe webhook errors.

Open issues from this session (non-code, user-side)

  • Apex DNS missing. resolutionflow.com (apex) returns no A/CNAME at the authoritative DNS (Namecheap per SOA dns1.registrar-servers.com.). When www was reconfigured in Railway, the apex record got dropped from the zone. www works (cert provisioned 2026-05-08 01:40 UTC, valid Let's Encrypt SAN). Symptom: apex unreachable from user's machine; Stripe verifier "URL couldn't be reached." User to re-add apex record at Namecheap (ALIAS Record host=@ value=c9g7uku8.up.railway.app) or re-add the apex as a Railway custom domain and follow Railway's DNS instructions. The Railway path is more durable.
  • Edge HSTS sticky state on user's machine. Browser remembers the earlier broken-cert visit. Fix: edge://net-internals/#hsts (delete resolutionflow.com and www.resolutionflow.com) + #dns clear host cache + #sockets flush. Cert IS valid on the wire (proven by curl --resolve returning 200 OK from the user's box).

Carry-forward

  • Annual pricing intentionally NOT implemented — user wants exit flexibility ("want to be able to exit if necessary without breaching any terms"). Schema columns (annual_price_cents, stripe_annual_price_id) preserved as nullable for future re-enable. sync_stripe_plan_ids.py leaves annual fields NULL.
  • INTERNAL_TESTER_EMAILS parsed comma-separated → normalized lowercase list. Anonymous callers always see the global flag — allowlist never leaks via unauthenticated request content (regression test enforces).
  • Office-hours design doc from this session at ~/.gstack/projects/chihlasm-resolutionflow/abc-feat-self-serve-signup-phase-2-design-20260507-112020.md. Captures the "documentation builder" thesis (cut branching Flows from pilot UI, focus on Day 1 onboarding checklist + 3 deep-capture procedures + Hudu/IT Glue/CW output). Pre-build assignment: 3 cold calls with external Directors of Onboarding before scoping the build. NOT yet adopted as roadmap — gated on the validation calls.
  • Frontend lint shows 3 warnings in coverage/ (auto-generated). Untouched.
  • Backend env: SALES_LEAD_RECIPIENT_EMAIL. Frontend env additions for cutover: VITE_SELF_SERVE_ENABLED, VITE_GOOGLE_CLIENT_ID, VITE_MS_CLIENT_ID, VITE_OAUTH_REDIRECT_BASE, VITE_CALENDLY_URL, VITE_STRIPE_PUBLISHABLE_KEY.