# Pricing Flow — Deploy Queue Tracker

> **Purpose.** When the **AI&Humn — Pricing Flow** fork finishes Features 1–11, this is the single source of truth for what needs to land on DEV + PROD. Maintained by Claude in the main worktree only; updated every time a pricing-feature commit merges into `main`.

**Last updated:** 2026-07-04 — synced to `d3c8ba2`; **prod domain cutover `aiandhumn.com` → `asqvox.com` staged.** ACTIVE runbook: **`deploy/ASQVOX-SAMEBOX-CUTOVER-RUNBOOK.md`** (fork-authored; same cPanel server, asqvox reuses the `aiw-prod` slot, fresh DB, aiandhumn apex → 301, 4 aiandhumn subdomains deleted to reclaim disk at 88%). `NEW-PROD-ENV-RUNBOOK.md` = reference for shared detail + new-VPS variant. **Ready nginx includes** (fill the runbook's manual "prepare asqvox includes" step): `deploy/cpanel-nginx/asqvox/*` (5 hosts) + `deploy/cpanel-nginx/aiandhumn-301-to-asqvox.conf`.
**VPS deploy baseline (shared box `65.254.80.37`):**
- **PROD** = `371c2f6` — migrations `0000 → 0028` applied (deployed 2026-06-29; first prod activation of Contact-Capture Sync)
- **DEV** = cafe-era `main` (cafe.aiworkfllow.com stood up 2026-06-29/30 from the `747fb54..3648635` cafe series — confirm exact SHA on the VPS with `git -C /opt/aiw/src log -1`)
- **origin/main = local main = `d3c8ba2`** — migrations `0000 → 0029` (30 journal entries, monotonic, verified; the Jul-04 `661f563..d3c8ba2` wave added NO migration and NO env var)

### Undeployed-to-PROD delta grew (`371c2f6` → `d3c8ba2`, 16 commits)
On top of the cafe vertical + `0029` (already tracked below), the **Jul-04 hardening wave** (8 commits) adds: cafe-widget greeting-shield / hold-on-pause / live-state UI / SDK-listener-dedupe / audit fast-follows (#6/#8/#9); **backend `trustProxy` narrowed** `true` → `['loopback','linklocal','uniquelocal']` (XFF-spoof fix #7 — ⚠️ Cloudflare-orange-cloud breaks `req.ip`, see runbook §2b); build-time cafe/DIY widget isolation; and the **aiandhumn noindex** SEO change. **Rebuild scope:** `api` (trustProxy) + `widget`/`cafe` (isolation + cafe-widget) + `marketing` (if any marketing copy in the wave); no migration to apply.

---

## 1. State snapshot (2026-07-02)

### Undeployed-to-PROD payload (`371c2f6` → `058c182`, 8 commits)
- **Cafe vertical**: backend voice-ordering relay, standalone `aiw-cafe-widget`, `boojee-cafe-app` dine-in SPA, cafe.* deploy infra (compose `cafe` service on :5176/:5186 + cPanel includes), menu-KB + ordering bugfixes
- **Migration `0029_widget_cafe_ordering`** (the only new one; prod DB is at `0028`)
- **New env var `AIW_CAFE_DISABLED`** (killswitch, default `false`) — both `.env.example` templates updated 2026-07-02 with this + all previously-missing vars (`WIDGET_CHALLENGE_SECRET` ⚠️ boot-blocking, `AIW_REQUIRE_*`, `AIW_CONTACT_SYNC_*`, spike-detection, `AIW_CRON_DISABLED`, `ENTERPRISE_LEAD_EMAIL`)
- Marketing `/about` + `/how-it-works` anchors

### What is live on PROD today (2026-06-29 deploy)
- Commit **`371c2f6`**, migrations **0000 → 0028** (29 entries / 29 applied / 0 drift)
- Everything through: Pricing F1-F11, co-browse Tier 0/1, Contact-Capture Sync (Stage 1→4, live but inert per widget until Retell wiring), anti-abuse Phase 1+4, marketing site on apex (SPA at app.aiandhumn.com), Asqvox rebrand
- Env posture: `PADDLE_API_BASE=https://api.paddle.com`, `AIW_CRON_DISABLED=false`, blank `RAZORPAY_*`/`PADDLE_*` pending KYC (503 fail-closed)

### Next major deploy = new environment
The next prod deploy is **not** an in-place update — it targets a **new dedicated server** with data migration + DNS cutover on `aiandhumn.com`. Full payload manifest, bootstrap, cutover, smoke matrix and rollback: **`deploy/NEW-PROD-ENV-RUNBOOK.md`**.

### Branch/stash hygiene (local)
`feature/cobrowse-t0-t1` + `prod-cobrowse-release` are stale (content absorbed into `main`) — never deploy from them; safe to delete. Stashes: `stash@{0}` (RetellVoiceClient transcript-debug WIP, 2026-06-26), `stash@{1}` (old handoff/tracker edits, superseded), `stash@{2}` (`F6-WIP-superseded-by-90bd310`, safe to drop).

---

## 1a. Hotfixes queued for next DEV deploy (2026-06-02 sync)

Eight commits landed on `origin/main` between `6b0e036` and `d90fa39`. **Zero new migrations, zero new env vars, zero new deps** — pure hotfix payload. Surfaced from hands-on DEV testing of F1-F11.

| Order | Commit | Type | Feature touched | Notes |
|---|---|---|---|---|
| 1 | `e2e8ef9` | fix | Admin escaping | Set-key button + other admin actions; backtick escape fix in `admin/index.html` |
| 2 | `0b71679` | fix | **F6** widget | W4 per-code visitor copy + W6 persisted quota cooldown (widget src + dist bundle rebuilt) |
| 3 | `23a6df8` | fix | **F1** plans-admin | Plans save was silently zeroing prices on every save (`plans.schemas.ts` + `plans.service.ts`) |
| 4 | `0d6d470` | fix | **F8** coupons-admin | Replaced broken raw `ANY()` SQL with `inArray` + real error logging (`admin-coupons.routes.ts`) |
| 5 | `49cd2f1` | fix | **F11** + **F5** admin | Email tab API-base fix + topup-sku error-logging surface |
| 6 | `1439050` | fix | Admin list endpoints | List endpoints expected nested `data.X` but `ok()` spreads flat |
| 7 | `84e6192` | feat | **Admin** restructure | Sidebar nav restructure + new Tenants page + plan/top-up assignment UI (`admin/index.html` +1543, `admin.routes.ts` +475) |
| 8 | `d90fa39` | refactor | **Admin** | Moved plan + top-up mutations to Tenants page (tenant-level resource) |

**Deploy SOP for this round (shortened — no migrations):**
```bash
cd /opt/aiw/src && git pull                                    # 6b0e036 → d90fa39
cd /opt/aiw/dev && docker compose build                        # api + widget rebuild; app + postgres CACHED
docker compose up -d                                           # recreates api + widget (app unchanged)
# Smoke: admin panel (X-Admin-Key) — Plans page, Coupons page, Tenants page, Email tab
# Smoke: F6 widget on a test site — verify per-code visitor copy + cooldown UX
```

**Affected images on rebuild:**
- `aiw-dev-api` — admin backend changes (plans/coupons/topups services + admin.routes)
- `aiw-dev-widget` — F6 widget bundle (`aiw-voice-widget.js` rebuilt)
- `aiw-dev-app` — no SPA-source changes; expect all CACHED
- `aiw-dev-postgres` — no DB changes

---

## 1b. Going-forward sync convention (fork → main)

The pricing-flow fork is pushing hotfixes directly to `origin/main` rather than going via a separate branch. To stay in sync:

1. **Start of every session**: `git -C /c/DD/AIWorkfllow/ProductVAPlugin fetch origin --prune && git log --oneline 6b0e036..origin/main` (replace `6b0e036` with last DEV deploy SHA) — shows what's queued.
2. **Categorize new commits** by (a) feature touched, (b) migration count, (c) env-var changes, (d) dep changes, (e) image-rebuild scope. Update §1a/§2 here.
3. **Never deploy without verifying** journal monotonicity (`grep '"when"' aiw-voice-backend/drizzle/meta/_journal.json` strictly ascending) — even hotfix commits can sneak migrations in.
4. **Always re-check `env.ts`** for new vars before deploy — these would be silent breakage otherwise.

---

## 2. Pricing-flow feature payload — running ledger

| # | Feature | Commit | Migrations | DEV | PROD |
|---|---|---|---|---|---|
| 1 | Dynamic geo-localized pricing | `bda2e20` | `0011_plans_pricing_extension` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 2 | Subscription model + period snapshots | `f56f398` | `0012_subscriptions` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 3 | Per-plan entitlements | `db17452` | (none — code-level filter) | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 4 | Replenishment cron + cycle audit | `3f88fa6` | `0019_cycle_closed_at` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 5 | Top-ups (SKU catalogue + FIFO + expiry) | `4be9a1e` | `0020_topups` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 6 | Quota gating (two-gate + Retell max_duration) | `90bd310` | `0021_widget_service_status`, `0022_topup_debited_per_cycle` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 7 | Tax module (Indian GST + Paddle MoR) | `0fee171` | `0015_tax_invoices` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 8 | Checkout + coupons | `eac88a7` | `0016_coupons`, `0017_plan_gateway_ids`, `0018_coupon_constraints` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 9 | Payment gateway adapters (Razorpay + Paddle) | `d0032dc` | `0013_payment_methods`, `0014_billing_idempotency` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 10 | Plan upgrade/downgrade/cancel/resume lifecycle | `75be9a6` | `0023_subscription_change_events` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| 11 | Email automation | `5ca3ddc` | `0024_email_automation` | ✅ 2026-06-01 | ✅ 2026-06-04 |
| — | Deploy hardening (multi-mig deploy path) | `980be47` | none | ✅ 2026-06-01 | ✅ 2026-06-04 |
| — | Deploy queue tracker (this file) | `c4ca09c` | none | ✅ 2026-06-01 | ✅ 2026-06-04 |

---

## 2a. DEV deploy 2026-06-01 — smoke matrix evidence

| F | Smoke check | Observed | Result |
|---|---|---|---|
| F1 | `GET /api/plans` | INR+USD prices, `detectedCountry:"US"`, `defaultCurrency:"USD"` | ✅ |
| F2 | 7 `/api/account/subscription*` routes | all → 401 | ✅ mounted |
| F3 | entitlements filter | flows through F2 surfaces (no direct route) | ✅ |
| F4 | API logs | `cron: scheduler registered` + `replenishment: tick complete (scanned: 0)` | ✅ ticking |
| F5 | `/api/account/topups/*` + expiry cron | 401 routes + `topup-expiry: no rows due to expire` | ✅ |
| F6 | service-status admin route + sessions quota | route present, code-level gate active | ✅ |
| F7 | `/api/account/tax-details` + `/api/account/invoices` | → 401 | ✅ mounted |
| F8 | `/api/billing/checkout/{preview,complete}` + `/coupons/validate` | → 401 | ✅ mounted |
| F9 | `/api/account/payment-methods/*` + webhooks | 401 + webhooks **503** (correct fail-closed) | ✅ |
| F10 | `change-plan/preview`, `change-plan`, `cancel`, `resume`, `cancel-pending-change` | → 401 | ✅ mounted |
| F11 | API logs | `email_dispatch_interval_ms: 120000` (2-min cron) | ✅ ticking |

**Migration apply log** (zero `[orphan-fix]`):
```
[apply] 0011 → 0024  (14 lines, in order)
Migrations complete: applied 14.
```
`db:status` → 25 entries / 25 applied / 0 drift.

---

## 3. Migrations queue (in apply order)

| Tag | From commit | Summary |
|---|---|---|
| `0010_password_reset` | `d1256c5` (on origin/main, NOT yet on VPS) | password-reset tokens table |
| `0011_plans_pricing_extension` | `bda2e20` | plans-table currency/geo columns + seed data |
| `0012_subscriptions` | `f56f398` | `subscriptions` + `subscription_billing_periods` + period snapshot |
| `0013_payment_methods` | `d0032dc` | `payment_methods` table |
| `0014_billing_idempotency` | `d0032dc` | `provider_webhook_events` dedup + UNIQUE (subscription_id, period_start) |
| `0015_*` | pricing fork — TBD | |
| `0016_*` | pricing fork — TBD | |
| … | | |

**Migration runner:** the self-healing migrator from `c6520a4`. **Verify in the migrate logs** that no `[orphan-fix]` lines appear — if any do, the chain was non-monotonic and something is wrong upstream.

**Pre-flight check (run on VPS before deploy):**
```bash
cd /opt/aiw/src && git pull
docker compose --project-directory /opt/aiw/dev run --rm api npm run db:status
# Or for prod: --project-directory /opt/aiw/prod
```

---

## 4. New env vars required before deploy

Append to each VPS `.env`:

### Both `/opt/aiw/dev/.env` and `/opt/aiw/prod/.env` (backend)
```env
# Feature 9 — payment gateways (leave BLANK to fail-closed with 503; fill once KYC done)
RAZORPAY_KEY_ID=
RAZORPAY_KEY_SECRET=
RAZORPAY_WEBHOOK_SECRET=
PADDLE_API_KEY=
PADDLE_WEBHOOK_SECRET=
PADDLE_API_BASE=https://sandbox-api.paddle.com   # prod → https://api.paddle.com

# Compose build-context (already in .env.example as of 980be47)
AIW_SRC=/opt/aiw/src
```

> The `PADDLE_API_BASE` is **allowlist-validated at boot** — only `api.paddle.com` or `sandbox-api.paddle.com` accepted. Misconfiguration crashes the API container on start, which is intentional (anti-SSRF).

### Frontend `.env.staging` / `.env.production` (already in repo, but verify)
```env
VITE_BILLING_ADD_CARD_ENABLED=false   # leave OFF until Razorpay/Paddle Add-Card roundtrip is verified live
VITE_MARKETING_WIDGET_BUNDLE_URL=...   # already there
```

### Pending features 3-8, 10-11 — env-var slots to fill in here as they land
_(append as fork merges arrive — common candidates: SMTP for receipts, webhook public URLs, currency-rate API key, …)_

---

## 5. Per-feature deploy checklist (fill in as each feature lands)

For every pricing-fork commit you add, capture:
- **Commit SHA** + one-line title
- **Migrations added** (filenames + 1-line each)
- **New env vars** (backend + frontend, with whether REQUIRED or optional)
- **New runtime deps** (`npm install` needed? Docker rebuild forced?)
- **External services to provision** (Razorpay account, Paddle webhook URL registration, etc.)
- **Feature flag default** (ON or OFF on first ship)
- **Smoke check** — single curl/SPA-click that confirms the feature is live

Already-captured rows:

### Feature 1 — `bda2e20`
- Migrations: `0011_plans_pricing_extension.sql`
- New env: none (backend) / no new VITE_ (uses existing `VITE_API_BASE_URL`)
- New deps: yes — backend `package.json` got 2 entries (run `npm install` inside `api` build, Docker auto-handles)
- External: none — geo lookup uses request headers, no IP-DB key
- Flag: ON by default (currency switcher visible on PricingPage)
- Smoke: `GET https://api.<env>/api/plans` → returns plans with `currency` array

### Feature 2 — `f56f398`
- Migrations: `0012_subscriptions.sql`
- New env: none new
- New deps: none
- External: none
- Flag: ON (Billing Overview page mounts at `/billing`)
- Smoke: SPA `/billing` loads for a logged-in user; `GET /api/subscription/me` returns 200 (null for users with no sub)

### Feature 9 — `d0032dc`
- Migrations: `0013_payment_methods.sql`, `0014_billing_idempotency.sql`
- New env: **REQUIRED before checkout works** — `RAZORPAY_*` + `PADDLE_*` (see §4). Safe to deploy with blanks: every gateway call throws `GatewayNotConfiguredError → 503`.
- New deps: none (native `fetch` + `node:crypto`)
- External:
  - Razorpay Dashboard → API keys + Webhook URL = `https://api.<env>/api/billing/webhooks/razorpay`
  - Paddle Dashboard → API key + Notification destination = `https://api.<env>/api/billing/webhooks/paddle`
- Flag: SPA Add-Card button gated by `VITE_BILLING_ADD_CARD_ENABLED` (default OFF)
- Smoke (with blanks): `GET /api/billing/payment-methods` returns 200 + `[]`; `POST /api/billing/payment-methods/add` returns 503 `GATEWAY_NOT_CONFIGURED`

### Feature N — _(template, copy-paste per feature)_
- Migrations:
- New env:
- New deps:
- External:
- Flag:
- Smoke:

---

## 6. Deploy SOP (chunk-by-chunk, mirrors 2026-05-23 protocol)

```bash
# 0. SSH to VPS as root or deploy user

# 1. Update source tree
cd /opt/aiw/src && git fetch && git log --oneline HEAD..origin/main   # see what's coming
git pull

# 2. Append new env vars to BOTH compose .env files (see §4)
vi /opt/aiw/dev/.env   # then /opt/aiw/prod/.env

# 3. Pre-flight DB status (NEW since 980be47)
docker compose --project-directory /opt/aiw/dev run --rm api npm run db:status

# 4. Build images (DEV first)
cd /opt/aiw/dev && docker compose build
# Verify image is fresh — pick one canary file and grep inside:
docker compose run --rm api sh -c 'wc -l /app/dist/lib/db/migrate.js && grep -c "orphan-fix" /app/dist/lib/db/migrate.js'

# 5. Run migrations explicitly (the `migrate` service runs once and exits)
docker compose run --rm migrate
# CHECK: every "[apply] NNNN_..." line should print exactly once.
# CHECK: NO "[orphan-fix]" lines — that would mean the chain was non-monotonic.

# 6. Bring stack up
docker compose up -d
docker compose ps   # all services Up; postgres healthy

# 7. Smoke tests
curl -sS https://api.aiworkfllow.com/health           # → 200 {"status":"ok"}
curl -sS https://app.aiworkfllow.com/ | head -5       # → SPA index
curl -sSI https://widget.aiworkfllow.com/aiw-voice-widget.js | head -3
# Plus per-feature smoke from §5 for each new feature

# 8. If clean → repeat steps 4-7 for /opt/aiw/prod
```

**Hard rules carried from 2026-05-23 deploy:**
- Verify the freshness of every built image with a unique-string grep before running migrations. CACHED layers are normal when the build context didn't change; verify the on-disk output, not the build log.
- One env at a time. Never `docker compose build` dev+prod in parallel on the same host.
- If `db:status` reports any drift before migrate, STOP and inspect — do not let the self-healing migrator paper over a real mistake on prod.

---

## 7. Post-deploy bookkeeping

After each deploy completes:
- [ ] Update **CLAUDE.md** "VPS Deployment" status block with new deploy date + commit SHA + migration range applied
- [ ] Update **handoff.md** top status banner
- [ ] Tick the row in §2 of this file from ✅ 2026-06-04 → ✅ shipped
- [ ] If `[orphan-fix]` lines appeared in migrate logs, root-cause **before** any further work — the monotonic invariant was broken
- [ ] Commit the doc updates with `docs: capture YYYY-MM-DD deploy …` per the going-forward protocol

---

## 8. Open coordination notes

- **`origin/main` is ahead of VPS by 7 commits as of 2026-05-30** (auth recovery flow + `0010_password_reset` + security/docs). The pricing-feature deploy will roll these up too — confirm before deploy that VPS is on `c6520a4` (current assumption) and not partially updated.
- **Local main has 4 unpushed commits** (`bda2e20 → 980be47`, Features 1, 2, 9 + deploy hardening). They must be pushed to `origin/main` **before** any VPS deploy. Per the trunk-only protocol, ask before pushing.
- The pricing fork should keep landing features in numerical order if possible — migrations 0015+ should preserve the strict monotonic `when` timestamp ordering the migrator validates.
