wip(handoff): pr #164 cutover blockers + doc refresh + dns triage
All checks were successful
Mirror to GitHub / mirror (push) Successful in 6s
CI / frontend (pull_request) Successful in 6m44s
CI / e2e (pull_request) Successful in 10m12s
CI / backend (pull_request) Successful in 10m33s

Updates HANDOFF.md, CURRENT_TASK.md, SESSION_LOG.md to reflect this
session's work: PR #164 ready for review (5 commits closing the last
self-serve cutover code blockers), Phase O manual-ops sequence as the
resume point, and the apex-DNS / Edge-HSTS issues open on the user's
side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 11:12:05 -04:00
parent 2c9f5e95ff
commit 25d124c6eb
3 changed files with 60 additions and 41 deletions

View File

@@ -1,10 +1,12 @@
# CURRENT_TASK.md
**Active task:** Self-serve signup Phase 2 — PR #162 is open on `feat/self-serve-signup-phase-2`. Current focus is resolving its failing Gitea checks. Phase O manual ops (Stripe live setup, internal validation, flag flip) remain pending after review/merge. See `.ai/HANDOFF.md` for the resume point.
**Active task:** Phase O cutover for self-serve signup. PR #164 (`feat/billing-plan-taxonomy`) open in Gitea with 5 commits at head `2c9f5e9`, closing the last code blockers — plan taxonomy reconciliation (`team``enterprise`, add `starter`), `INTERNAL_TESTER_EMAILS` allowlist, `sync_stripe_plan_ids.py` script, page-title `—` JSX-escape bug fix, frontend taxonomy followups, doc refresh. After merge, only manual ops remain: Stripe Dashboard live-mode config, Railway prod env vars, internal validation pass, public flag flip. See `.ai/HANDOFF.md` for the resume point.
## Recently shipped
- **2026-05-06`feat/self-serve-signup-phase-2`** Phase 2 frontend cutover code (Tasks 2744 of the plan, 18 commits). Backend remainders + frontend billing foundation + auth surfaces (OAuth + accept-invite + verify-email) + welcome wizard + dashboard redesign (TrialPill, NextStepCard, unified checklist) + public surfaces (`/pricing`, `/contact-sales`) + beta-signup deprecation. Phase O (Stripe live setup, internal validation, flag flip) is operational and pending. Single alembic head `c6cbfc534fad` (no new migrations).
- **2026-05-08PR #164 (open)** Plan taxonomy reconciliation + `INTERNAL_TESTER_EMAILS` allowlist + Stripe sync script + page-title fix + frontend taxonomy followups + doc refresh. 5 commits on `feat/billing-plan-taxonomy` from main (`dad5e1f`); HEAD `2c9f5e9`. Migration `4ce3e594cb87` renames `plan_limits.plan='team'``'enterprise'` and adds `starter` row (caps interpolated between free and pro: `max_trees=10`, `sessions=75`, `ai=15/mo`). Resource visibility (`Tree.visibility='team'`, `StepLibrary.visibility='team'`) is a separate domain and intentionally untouched. New `backend/scripts/sync_stripe_plan_ids.py` upserts `plan_billing` rows from Stripe products by exact name match — annual fields stay NULL by design (user explicitly skipping annual pricing for exit flexibility). `Settings.is_internal_tester` + `is_self_serve_active_for` centralize the allowlist + global-flag check; new `get_current_user_optional` dep; `/config/public` honors allowlist for authenticated callers; `/auth/register` allows allowlisted emails without invite code. LandingPage page-title bug — `—` inside JSX attribute strings was rendering as 6 literal characters in browser tabs; replaced with literal em dash. PageMeta default tagline updated from "Decision Tree Platform" to "AI-Powered Troubleshooting for MSPs". 86/86 passing across subscription/billing/plan/invite/admin sweep; tsc + lint clean. See `.ai/DECISIONS.md` for the two architectural entries (taxonomy reconciliation, allowlist).
- **2026-05-06 — PR #163** Seed test users marked email-verified. Squash-merged into main as `dad5e1f`.
- **2026-05-06 — PR #162** Self-serve signup Phase 2 (frontend cutover). 18 commits across Tasks 2744 of the plan. Backend remainders + frontend billing foundation + auth surfaces (OAuth + accept-invite + verify-email) + welcome wizard + dashboard redesign (TrialPill, NextStepCard, unified checklist) + public surfaces (`/pricing`, `/contact-sales`) + beta-signup deprecation. Squash-merged into main as `f1be3ab`. Single alembic head was `c6cbfc534fad` (no new migrations in Phase 2; PR #164 adds `4ce3e594cb87`).
- **2026-05-02 — PR #159** In-product User Guides rewrite. Merged into `main`. Replaced 15 feature-dump guides with 43 problem-oriented Diátaxis how-tos grouped under 10 categories. Dropped Maintenance Flows / AI Assistant / Flow Assist Sparkles (UI no longer exists). Renamed Step Library → Solutions Library. Authored 14 net-new how-tos for FlowPilot-era surfaces (tasklane keyboard flow, what-we-know, resolve, escalate, record-fix-outcome, post-docs-to-ticket, share-update, pause-and-leave, build-script-from-scratch, open-suggested-flow, pin-a-flow, invite-teammate, etc.). Schema additions: `category`, optional `relatedSlugs`; hub renders category sections; detail page renders related-guides footer. Fixed rendering bug where `**bold**` in `step.tip` rendered literally. Killed misleading "N sections" subtitle on guide cards. Browser-verified against engineer + owner login (sidebar labels, account sub-pages, pilot-screen header buttons, Tasks panel, integration form). Two unverified items intentionally deferred: change-teammate-role (requires non-owner test member to inspect role-change control) and detailed Resolve / Escalate modal contents (Resolve gated by 6 pending tasks in test data). tsc and Vite build clean.
- **2026-05-01 — PR #158** Session-screen UX impeccable pass + tasklane keyboard flow. Merged into `main` as `5e10005`.
- **Impeccable pass** (5 sub-passes — distill / quieter / layout / typeset / polish): score 24/40 → 33/40. Removed the duplicate "Suggested checks" chip strip; added an inline `Next steps · N pending in Tasks` cue above the latest action-bearing AI bubble; consolidated the desktop session header to Resolve + Escalate + ⋯ kebab (Context / New Ticket / Update Ticket / Pause now under the kebab, mobile kebab gained Context + New Ticket parity); centered the messages column to `max-w-3xl` to match the composer; bubbles dropped to `rounded-xl`. Decoration sweep: dropped 3px side stripes (TaskLane done states, all 6 ProposalBanner modes, WhatWeKnowItem rows), gradient backgrounds (WhatWeKnow + every banner), accent borderTop on TaskLane header, backdrop-blur on handoff overlay, animate-pulse-amber ring in VerifyingBanner, bordered avatar boxes in banners. Type sweep: 14 distinct sizes → 5-step scale (10/11/12/13/14px). Icon disambiguation: `MessageCircleQuestion` split into `Pencil` (Answer CTA) + `HelpCircle` (per-check explainer). Dead `font-sans` audit (12 sites) and double `text-xs` cleanups.

View File

@@ -2,56 +2,47 @@
# HANDOFF.md
**Last updated:** 2026-05-07 (PR #162 CI investigation/fixes)
**Last updated:** 2026-05-08
**Active task:** PR #162 (`feat/self-serve-signup-phase-2`) is open in Gitea. Current session is resolving its failing checks.
**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 #162 originally failed quickly in Gitea CI. Public Gitea status metadata was available, but job logs redirected to login and no `GITEA_TOKEN` was present. The branch was pushed over SSH.
PR #164 commits (oldest → newest):
Fixed environment drift first:
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.
- Standardized backend native/dev/CI Python on 3.12.13 to match Docker.
- Added `.python-version`.
- Rebuilt `backend/venv` from pyenv Python 3.12.13 and verified native `pytest --version` / `alembic --version` with explicit local env.
- Updated Gitea CI backend/e2e Python setup to 3.12.
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`.
Fixed Gitea runner assumptions next:
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").
- Added `actions/setup-node@v4` with Node 20 to Gitea frontend and e2e jobs.
- Pushed `fix(ci): set up node in gitea workflow`.
Local frontend validation then exposed real lint failures in Phase 2 React code under the current lint stack. The current WIP fixes:
- `react-refresh/only-export-components` for exported pure helpers used by tests/shared invite OAuth code.
- `react-hooks/set-state-in-effect` warnings where local state intentionally mirrors route/config/cache state.
- `react-hooks/purity` warnings from `Date.now()` during render.
- Redundant loading-state write in pricing page.
Validation after those frontend changes:
- `docker exec -w /app resolutionflow_frontend npm run lint` passed.
- `docker exec -w /app resolutionflow_frontend npm run test:coverage` passed (`198` tests).
- `docker exec -w /app -e NODE_OPTIONS=--max-old-space-size=4096 resolutionflow_frontend npm run build` passed.
Known local noise:
- React `act(...)` warnings appeared in existing tests during coverage but did not fail the suite.
- Vite emitted large chunk warnings during build.
- Unrelated dirty/untracked files remain and should not be staged unless explicitly requested: `docker-compose.dev.yml`, `.env.example`, `abc-feat-self-serve-signup-phase-2-design-20260507-112020.md`, `core.*`, `docs/architecture/`, `docs/tutorials/`.
Single alembic head: `4ce3e594cb87` after PR #164 merges (was `c6cbfc534fad`).
## Resume point
1. Commit the frontend lint fixes and `.ai/` handoff updates with the required Codex trailer.
2. Push `feat/self-serve-signup-phase-2`.
3. Poll Gitea PR #162 statuses for the new head SHA:
`curl -fsSL https://gitea.resolutionflow.com/api/v1/repos/chihlasm/resolutionflow/statuses/<sha> | python -m json.tool`
4. If statuses are still pending, report that local frontend CI is green and Gitea runner work is queued/running. If a check fails, public statuses may show only the context/description; logs require authenticated Gitea access.
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
- Phase O manual ops remain pending after PR review/merge: Stripe live setup, internal validation, feature-flag flip.
- Backend env: `SALES_LEAD_RECIPIENT_EMAIL`.
- Frontend env: `VITE_SELF_SERVE_ENABLED`, `VITE_GOOGLE_CLIENT_ID`, `VITE_MS_CLIENT_ID`, `VITE_OAUTH_REDIRECT_BASE`, `VITE_CALENDLY_URL`.
- Single alembic head remains `c6cbfc534fad`; Phase 2 added no migrations.
- 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`.

View File

@@ -12,6 +12,32 @@
---
## 2026-05-08 03:30 UTC — Claude — PR #164 self-serve cutover code blockers, doc refresh, page-title bug, DNS triage
**Accomplished:**
- Merged PR #162 (self-serve Phase 2 frontend) and PR #163 (seed users email-verified) into main via Gitea API squash merge. Created branch `feat/billing-plan-taxonomy` off the new main; pushed 5 commits closing the last code blockers for Phase O cutover. PR #164 opened at gitea pulls/164.
- Plan taxonomy reconciliation. Discovered the marketing surface (PricingPage, Stripe products) was wired for `Starter / Pro / Enterprise` while backend was on `free / pro / team`; `BillingPlan` schema's `Literal["pro","starter","team","enterprise"]` could accept FK-violating values; `plan_billing` was unseeded. 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 (`max_trees=10`, `sessions=75`, `users=1`, `ai=15/mo`, no KB Accelerator, no custom branding, no priority support). Code rename across schemas (`invite_code`, `billing`, `admin`, `subscription`), `Subscription` paid-plan/`has_pro_entitlement` checks, `admin_dashboard.py`, `admin.py`, frontend `useSubscription.isPaidPlan`. Resource visibility (`Tree.visibility='team'`, `StepLibrary.visibility='team'`) is a separate domain (means "shared with my account") and intentionally untouched. 86/86 passing across subscription/billing/plan/invite/admin sweep after the rename. Conftest plan_limits seed + `_seed_plan_limits` helper made a true upsert.
- New `backend/scripts/sync_stripe_plan_ids.py` — idempotent upsert from Stripe products by exact name match (`ResolutionFlow Starter / Pro / Enterprise`), picks active monthly recurring price, leaves annual fields NULL by design. Works against test or live keys via `STRIPE_SECRET_KEY`. Run against test mode populated `plan_billing` for all 3 tiers in dev DB. Annual pricing intentionally skipped per user's exit-flexibility constraint.
- Stripe MCP work (test mode, `livemode=false`): archived leftover Enterprise `$500/mo` test price (had to clear the product's `default_price` first — Stripe blocks archive otherwise). Verified test-mode product set: Starter $19.99/mo, Pro $29.99/mo, Enterprise no price (sales-led).
- `INTERNAL_TESTER_EMAILS` allowlist. Phase O Task 46 needed it as a code blocker (flagged in prior SESSION_LOG as "backend support is NOT yet built"). `Settings.is_internal_tester` (case-insensitive membership) + `is_self_serve_active_for(email)` (returns global flag OR allowlist hit) centralize the check. New `get_current_user_optional` dep — best-effort auth that returns `None` instead of 401, used by `/config/public` so the same endpoint serves anonymous and authed. `/config/public` returns `self_serve_enabled=true` for authenticated allowlist members; `/auth/register` allows allowlisted emails without invite code. 5 regression tests including "anonymous callers always see the global flag" (prevents leak via unauthenticated request content).
- Stripe env passthrough: `docker-compose.dev.yml` now wires `STRIPE_*` + `SELF_SERVE_ENABLED` + `INTERNAL_TESTER_EMAILS` into the backend container. New repo-root `.env.example`. `backend/.env.example` updated with the self-serve cutover vars.
- Page-title bug fix on `LandingPage.tsx`. Two JSX attribute strings (`title="..."`, `description="..."`) had `—` (six literal characters) — JSX attribute strings don't process JS escape sequences, so the browser tab and OG description rendered the literal text instead of an em dash. Replaced with the literal em dash character. Verified by grep — every other `\u...` in the codebase is inside a real JS string (`'...'` literal or `{...}` JSX expression) where escapes resolve at compile time. PageMeta default tagline updated from stale "Decision Tree Platform" to "AI-Powered Troubleshooting for MSPs" (matches index.html and brand positioning).
- Frontend taxonomy followups (caught by tsc -b after rebuild). The earlier taxonomy commit didn't propagate through frontend types: `types/account.ts`, `types/admin.ts`, `types/billing.ts`, `admin/AccountsPage.tsx` (state type, select onChange cast, `<option value="team">` rendered UI), `admin/InviteCodesPage.tsx` (PLAN_OPTIONS array, state type, onChange cast), `AccountSettingsPage.tsx` (`plan !== 'team'` check + CheckoutButton prop), `subscription/CheckoutButton.tsx` (prop type + planLabels). All updated to `'free' | 'pro' | 'starter' | 'enterprise'`. tsc clean. Lint clean (3 warnings only in auto-generated `coverage/`).
- Doc refresh commit (`docs: refresh CURRENT-STATE, ROADMAP, README, DECISIONS for self-serve cutover`). CURRENT-STATE bumped to 2026-05-07; added entries for PR #159164; refreshed What's In Progress / What's Next around Phase O. ROADMAP got a "Status as of 2026-05-07" preamble (months-stale historical content kept underneath as record); In Progress and What's Next sections updated. README fixed legacy `patherly_postgres` Docker command, project-tree path, `UI-DESIGN-SYSTEM.md` reference; added `AGENTS.md`, `PROJECT_CONTEXT.md`, `PRODUCT.md` to docs table. DECISIONS appended two entries (taxonomy reconciliation, allowlist).
- Office-hours session ran via `/office-hours` skill earlier in this session. Design doc saved at `~/.gstack/projects/chihlasm-resolutionflow/abc-feat-self-serve-signup-phase-2-design-20260507-112020.md`. Captured the "documentation builder" thesis — cut branching Flows from pilot UI, focus product around FlowPilot + Day 1 onboarding checklist as navigational frame + 3 deep-capture procedures (M365 tenant build, Windows server build, credential vault) + Hudu/IT Glue/ConnectWise output. Founder is a Director-of-Onboarding at his own MSP (Andrea Henry); pre-build assignment is 3 cold calls with external Directors of Onboarding before scoping. NOT yet adopted as roadmap.
- DNS / cert triage: `www.resolutionflow.com` was unreachable (Railway "train hasn't arrived" page) — user added it as a custom domain in Railway, cert provisioned at 2026-05-08 01:40 UTC, `www` now serves 200 with valid Let's Encrypt SAN. Apex `resolutionflow.com` separately discovered to have NO A/CNAME at authoritative DNS (Namecheap per SOA `dns1.registrar-servers.com.`). When user reconfigured `www`, the apex record dropped from the zone. From Railway-edge IP both names work fine when DNS is forced (proven by `curl --resolve` returning 200 OK from user's box) — so the apex cert is also valid; the failure mode is purely DNS-level absence. User asked for HSTS clearance steps in Edge — provided `edge://net-internals/#hsts`, `#dns`, `#sockets` walkthrough plus Linux DNS flush options.
**Left for next session:**
- Verify PR #164 CI green, then squash-merge.
- Phase O manual ops sequence (Stripe Dashboard live-mode setup, Railway prod env vars including `INTERNAL_TESTER_EMAILS`, run `sync_stripe_plan_ids.py` against prod, internal validation Task 46, flag flip Task 47, PostHog dashboards, Sentry alert).
- User-side: re-add apex DNS record at Namecheap (ALIAS `@``c9g7uku8.up.railway.app`, or re-add apex in Railway), clear Edge HSTS state.
**Files touched (all on `feat/billing-plan-taxonomy`, all pushed):** `backend/alembic/versions/4ce3e594cb87_add_starter_rename_team_to_enterprise.py` (new), `backend/scripts/sync_stripe_plan_ids.py` (new), `backend/app/{schemas/{billing,invite_code,admin,subscription}.py, models/subscription.py, api/{deps.py, endpoints/{auth.py, admin.py, admin_dashboard.py, config.py}}, core/config.py}`, `frontend/src/{components/{common/PageMeta.tsx, subscription/CheckoutButton.tsx}, hooks/useSubscription.ts, pages/{LandingPage.tsx, AccountSettingsPage.tsx, admin/{AccountsPage.tsx, InviteCodesPage.tsx}}, types/{account.ts, admin.ts, billing.ts}}`, `backend/tests/{conftest.py, test_admin_plan_limits.py, test_invite_plan.py, test_plans_public.py, test_config_public.py}`, `docker-compose.dev.yml`, `.env.example` (new), `backend/.env.example`, `CURRENT-STATE.md`, `03-DEVELOPMENT-ROADMAP.md`, `README.md`, `.ai/{DECISIONS.md, HANDOFF.md, CURRENT_TASK.md, SESSION_LOG.md}`.
---
## 2026-05-07 11:45 EDT — Codex — Push PR #162 CI runner setup fixes
- Inspected Gitea PR #162 via public API. PR head was `380fcf7` and all CI jobs failed quickly; pushed local commits through `4a37a47`, including Python 3.12 setup for Gitea backend/e2e jobs.