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

49 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- Keep under ~2K tokens. Old handoffs live in SESSION_LOG.md. Do not let this file accumulate 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 cutover``Settings.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 types``LandingPage.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`.