Files
resolutionflow/.ai/HANDOFF.md
Michael Chihlas dc22aa0ff0
All checks were successful
Mirror to GitHub / mirror (push) Successful in 5s
CI / frontend (pull_request) Successful in 6m42s
CI / e2e (pull_request) Successful in 10m8s
CI / backend (pull_request) Successful in 10m31s
docs(handoff): record PR #164/#165/#167 merges, EIN blocker, pending bug
PR #164 (taxonomy + Stripe sync + allowlist) merged as 3f04911.
PR #165 (legal/contact pages + MarketingFooter) merged as ba45cfe.
PR #167 (create_site_admin.py bootstrap script) merged as e50a215.

All code blockers for self-serve cutover are now on main. Site-admin
bootstrap script verified end-to-end against prod via railway ssh
(first prod super-admin row now exists).

Stripe live-mode activation blocked on EIN — user applying via
IRS.gov on 2026-05-13. Mailing-address decision: home address into
Stripe's private business profile temporarily; public-facing
ContactPage/PoliciesPage stays "available on request" until the
P.O. Box arrives.

Records a pending bug: user reported finding one but did not share
details — planning to send a screenshot via the VS Code extension
GUI in the next session. Next-session-first-action is updated to
capture and triage that screenshot before resuming Phase O.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 11:17:30 -04:00

61 lines
9.4 KiB
Markdown

<!-- Keep under ~2K tokens. Old handoffs live in SESSION_LOG.md. Do not let this file accumulate history. -->
# HANDOFF.md
**Last updated:** 2026-05-12
**Active task:** Phase O cutover for self-serve signup. All code blockers are closed on `main` (PR #164 `3f04911`, PR #165 `ba45cfe`, PR #167 `e50a215`). **Currently blocked on Stripe live-mode activation — root cause is EIN, not code.** User does not yet have an EIN for ResolutionFlow, LLC; Stripe requires a tax ID for live-mode activation. Applying via IRS.gov on 2026-05-13. Mailing-address decision made 2026-05-12: user will enter home address into the Stripe business profile temporarily so live-mode isn't blocked on the P.O. Box; the **public-facing** mailing-address `TODO` in `ContactPage.tsx` and `PoliciesPage.tsx` stays "available on request" until the P.O. Box is set up (do NOT put the home address on the public site). Stripe address can be updated to the P.O. Box later without re-verification. Apex DNS at Namecheap is still missing (separate user-side issue tracked below); only matters once Stripe runs its site-verification step, which happens after the business-profile fields are accepted. Nothing on the code side blocks live-mode flip.
**Bug pending capture (2026-05-12):** User reported finding a bug during the session but did not provide details — planning to send a screenshot via the VS Code extension GUI in the next session. Ask for the screenshot at session start, then triage. No further context yet.
## Where this session ended
PR #167 merged (`e50a215 Merge pull request '...create_site_admin.py...'`). Bootstrap script for the site-wide super-admin: `backend/scripts/create_site_admin.py`. Idempotent — creates or promotes a `super_admin` on any env. Reads `--email`, optionally `--send-reset` (mails the reset link) or `--print-reset` (prints to stdout) or `--promote-only`. Uses `ADMIN_DATABASE_URL` for BYPASSRLS. Verified end-to-end against the deployed Railway backend container by the user via `railway ssh` ("we're good now" confirmation, 2026-05-12 evening). Intended prod invocation when Stripe/EIN clears: `railway run python -m scripts.create_site_admin --email michael@resolutionflow.com --send-reset` from inside the backend container shell (not local Windows — local Python lacks the dep tree).
Earlier in the session, PR #165 squash-merged (`ba45cfe feat(legal): add /policies, /contact, /promotions pages + MarketingFooter (#165)`):
- **New pages**, all SPA, matching existing `/privacy` and `/terms` pattern: `/policies` (consolidated Customer Policies — customer service contact, return/refund/dispute policy, cancellation, U.S. legal and export restrictions, promotional terms; anchor IDs per subsection), `/contact` (phone **(470) 949-4131**, support/sales/billing/security inboxes, response-time SLAs), `/promotions` (stub stating no promotions currently active — satisfies Policies §6.2 cross-ref).
- **`MarketingFooter`** (`frontend/src/components/common/MarketingFooter.tsx`) extracted from inline landing footer and mounted on `/landing`, `/pricing`, `/contact-sales`. Reuses existing `landing-footer*` CSS — must be rendered inside a `.landing-page` wrapper (documented in a JSX comment) because `--lp-*` vars are scoped there. All four legal links (Privacy / Terms / Policies / Contact) are now reachable from every marketing surface.
- **Privacy and Terms closing sections** updated to point at `/contact` + `/policies` and the correct inbox per area (`security@` and `support@` respectively). Stale `hello@resolutionflow.com` mailto removed everywhere.
- **Mailing address** left as TODO comments in `ContactPage.tsx` and `PoliciesPage.tsx` (one each). Rendered publicly as "available on request — email support@". Fill in when the P.O. Box is purchased.
`tsc --project tsconfig.app.json --noEmit` and `eslint` clean. Local `vite build` and `tsc -b` are blocked by root-owned `node_modules/.tmp` and `node_modules/.vite-temp` cache directories — CI rebuilds from a clean env and was green.
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 handoffs as "do not stage").
Single alembic head: `4ce3e594cb87` (no schema changes in this PR).
## Resume point
**First thing next session:** ask the user for the bug screenshot (mentioned at end of 2026-05-12 session — they were planning to send it via VS Code extension GUI). Triage that before resuming Phase O work.
After that — **Phase O manual ops, all user-side, all gated on EIN landing first:**
1. **EIN application** (user, 2026-05-13 via IRS.gov). Without this, Stripe live-mode can't activate.
2. **Stripe Dashboard live-mode** (once EIN is in hand):
- 3 Products (Starter, Pro, Enterprise). 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.
- **Business profile fields**: Customer service URL `https://resolutionflow.com/contact`. Refund/cancellation policy URL `https://resolutionflow.com/policies`. Terms `https://resolutionflow.com/terms`. Privacy `https://resolutionflow.com/privacy`. Phone `(470) 949-4131`. Mailing address = user's home address temporarily (private Stripe field; will swap to P.O. Box later without re-verification). EIN = the newly-issued tax ID.
3. **Apex DNS fix at Namecheap** (re-add `@` ALIAS → `c9g7uku8.up.railway.app`, or re-add apex as a Railway custom domain). Becomes the next blocker once Stripe runs its site-verification step.
4. **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.
5. **Bootstrap prod super-admin** via the new `create_site_admin.py` script (PR #167, on main as `e50a215`). Run from inside the backend container shell (not local Windows): `railway ssh --service=<backend-service-name>` then `python -m scripts.create_site_admin --email michael@resolutionflow.com --send-reset`. Reset email arrives at `michael@resolutionflow.com`, user clicks the link, sets a password, logs in as super_admin. Idempotent — safe to re-run.
6. **Sync Stripe → DB**: `railway run python -m scripts.sync_stripe_plan_ids` (or via `railway ssh` for the same reason as #5). Verify `plan_billing` rows have `sk_live_*` price IDs.
7. **Internal validation (Phase O Task 46)**: 9 scenarios with internal testers whose emails match `INTERNAL_TESTER_EMAILS`.
8. **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 prior 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.
## Carry-forward
- Annual pricing intentionally NOT implemented — user wants exit flexibility. Schema columns preserved as nullable. `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 at `~/.gstack/projects/chihlasm-resolutionflow/abc-feat-self-serve-signup-phase-2-design-20260507-112020.md` (documentation-builder thesis). NOT yet adopted as roadmap — gated on 3 cold calls with external Directors of Onboarding.
- Mailing address fill-in: search for `TODO: replace with full mailing address` in `frontend/src/pages/ContactPage.tsx` and `frontend/src/pages/PoliciesPage.tsx` (one each) once P.O. Box is purchased.
- `backend/scripts/create_site_admin.py` is the durable site-admin bootstrap tool — use for first prod admin, recovery, or promoting future teammates. Idempotent. Three modes: `--send-reset`, `--print-reset`, `--promote-only`. Run from inside the deployed backend container via `railway ssh`, not from local Windows (local Python lacks the dep tree).
- Bot-crawlability of legal pages: still SPA-rendered. Stripe didn't enforce content scraping last time (issue turned out to be DNS). If a future vendor review flags it, pre-render with `vite-plugin-prerender-spa` (~half day).
- 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`.