From 2e0daad06e59ba543077a95778d90608b8798633 Mon Sep 17 00:00:00 2001 From: Siddhant Mundra Date: Wed, 27 May 2026 12:44:31 +0530 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20productization=20design=20spec=20?= =?UTF-8?q?=E2=80=94=203-5=20design=20partners,=20separate=20repo,=20full-?= =?UTF-8?q?prototype-to-prod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brainstormed scope for turning the prototype into a real multi-tenant SaaS product: - audience: 3-5 design partners, no billing yet - isolation: separate git repo so the demo at procurement-manager.vercel.app stays frozen - approach B: harden every existing feature (digest, inbox, RFQ, vendors, opportunities, forecasts, agents) - ingestion: real Gmail OAuth + WhatsApp Cloud/Periskope + existing chat-export upload - 4 agents in propose-only mode (vendor-discovery deferred) - lean SaaS ops: Sentry, Better Stack, Neon backups, runbook - logging + unit tests are first-class from phase 0 10-12 week phased plan included. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...7-productize-tradyon-procurement-design.md | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md diff --git a/docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md b/docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md new file mode 100644 index 0000000..1c67f13 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md @@ -0,0 +1,214 @@ +# Productize Tradyon Procurement — Design Spec + +**Date:** 2026-05-27 +**Author:** Siddhant Mundra + Claude (brainstorming session) +**Status:** Approved — ready for execution planning + +--- + +## 1. Goal + +Take the current Tradyon Procurement prototype (the codebase at `Sidgit11/ProcurementManager`, deployed at `procurement-manager.vercel.app`) and turn it into a real, multi-tenant SaaS product usable day-to-day by **3–5 design partners** in the Polico ICP band (mid-market commodity traders, processors, importers). + +**Audience for v1:** 3–5 design partners, hand-onboarded but using a fully self-serve Clerk sign-up flow. No billing — Tradyon eats infrastructure cost during the design phase. + +**Bar:** "Lean SaaS" reliability — partners can run real procurement on it, but no formal SLA, no SOC2. + +--- + +## 2. Critical constraint: do not break the demo + +The current deployment at `procurement-manager.vercel.app` is actively used for sales demos. **It must remain bit-for-bit untouched** through this entire effort. + +**Isolation strategy:** new git repo entirely. + +- New repo: `TradyonProcurementProd` (private). +- Old repo (`Sidgit11/ProcurementManager`) is frozen as the demo. Branch protection on `main`. README banner pointing to the new prod repo. +- New Vercel project linked to the new repo, with its own Neon Postgres DB, its own Clerk application, its own Anthropic API key, its own domain (`app.tradyon.ai` recommended). +- No code or infrastructure is shared between demo and production. Future improvements to the demo (cosmetic tweaks for sales) are ported manually. + +--- + +## 3. Scope + +### 3.1 In scope (every feature in the current prototype must work for real) + +- All app pages: digest, inbox, compare, vendors (pool + curated Discover), RFQ engine, opportunities, POs, forecasts + workshop, agents, alerts, notifications, ask, settings, vendor vault, onboarding. +- All 4 LLM agents: daily summary, buy-now scanner, follow-up nudges, negotiation drafter. (Vendor-discovery agent stays as a curated list — no shipment-data integration.) +- Real ingestion: Gmail OAuth, WhatsApp Chat Export upload (already works), WhatsApp Cloud API outbound + inbound capture (with Periskope as a swap-in fallback). +- Multi-tenant with Clerk fully on (DEMO_MODE removed). +- Lean SaaS ops: Sentry, Better Stack uptime, Neon backups, structured logs, runbook. +- Comprehensive unit + integration tests (target ~70% coverage), CI on PR. + +### 3.2 Out of scope (explicit, in writing) + +- Voice-note transcription (Whisper). Voice messages show in the inbox as a link back to WhatsApp. +- Outlook OAuth ingestion. +- Real shipment-data vendor discovery (Importgenius / Panjiva / Volza). +- Billing / Stripe. +- Inbound supplier PDF OCR (PDFs go to vendor vault for manual review). +- SOC2 / formal compliance. +- Mobile app. + +--- + +## 4. Architecture overview + +### 4.1 Bootstrap + +| Concern | Choice | +|---|---| +| Git host | GitHub, new private repo | +| Initial commit | Squash of `Sidgit11/ProcurementManager@98f3224` so the new repo starts clean | +| Hosting | Vercel (new project) | +| DB | Neon Postgres via Vercel Marketplace (new project) | +| Auth | Clerk (new Clerk application under same Clerk org) | +| LLM | Anthropic, dedicated API key for the new project | +| Domain | `app.tradyon.ai` (with `*.vercel.app` as bootstrap) | +| Error tracking | Sentry | +| Uptime | Better Stack | +| Cron | Vercel Cron (already used) | + +### 4.2 Multi-tenancy + +- `DEMO_MODE=1` removed from the Vercel project. All demo-mode branches (`isDemo()`, `currentOrg()` fallback, `scan.ts` early return, `demo-extract.ts`, `upload/route.ts` synchronous tick) are deleted; the real path is the only path. +- Clerk sign-up flow: user creates an account → Clerk webhook hits `/api/clerk/webhook` (signature-verified) → creates a row in `org` and `user`. The current webhook handler exists but is stubbed; needs real implementation. +- Every server-rendered page already calls `currentOrg()`. As part of bootstrap, an audit pass walks every `db.select()` to confirm it filters by `org_id`. Coverage backed by a vitest helper that exercises each page from two test orgs and asserts no cross-tenant rows appear. +- Schema is already multi-tenant from day one (every business table has `org_id`). + +### 4.3 Ingestion + +**Gmail OAuth** +- New table `gmail_connection { id, org_id, user_id, email, access_token_enc, refresh_token_enc, history_id, scopes[], status, last_synced_at }`. +- Encryption: libsodium with a project-scoped secret in Vercel env (`GMAIL_TOKEN_KEY`). One key per project — rotation is a manual procedure documented in the runbook. +- New endpoints: `POST /api/auth/gmail/start`, `GET /api/auth/gmail/callback`, `POST /api/auth/gmail/disconnect`. Connect button lives in `/settings/integrations`. +- Pull strategy: Vercel Cron every 5 minutes runs `/api/cron/gmail-poll`. Per active connection, calls Gmail History API with `historyId`. Upserts new `message` + `thread` rows. Enqueues `extraction_job` for each new message. +- First connect does a bounded 90-day historical sync. +- Outbound: RFQ send routes through the user's Gmail (uses the OAuth token, so `From:` is the user's own address). + +**WhatsApp** +- Env-gated provider: `WHATSAPP_PROVIDER=cloud|periskope`. Cloud is the target; Periskope is a fallback if Meta verification stalls. +- New adapter interface: `WhatsappAdapter { sendMessage(input), verifyWebhook(req), parseWebhook(req) }`. Both Cloud and Periskope implementations live behind it. +- Webhook: `POST /api/webhooks/whatsapp`. Signature verified per provider. +- Inbound messages upserted to `message` + `thread`, extraction enqueued. +- Outbound: RFQ + nudge messages sent via the active provider. +- WhatsApp Chat Export upload: unchanged from the current prototype. + +**Extraction** +- `demo-extract.ts` deleted. Anthropic-based `extract.ts` (already exists in the prototype) becomes the only path. +- Cost guard: per-org daily budget in `org.settings.anthropicDailyBudgetUsd` (default $5). Soft-fail (queue + warn the org owner via in-app notification) when exceeded. +- Job runner: `extraction_job` table exists. Move from inline `tickOnce(200)` (the demo upload pattern) to a background cron `/api/cron/extraction-tick` every 30 s. + +### 4.4 Agents + +All four agents run in **propose-only mode for v1** — the `auto_execute` column on `agent_policy` stays `false` until a partner explicitly opts in. + +| Agent | Trigger | Output | Failure mode | +|---|---|---|---| +| Daily summary | Cron 06:00 BRT per org | Writes `agent_run.proposed_actions.summary` rendered on `/digest` hero. Uses Claude Haiku for cost. | UI falls back to the existing computed fallback string — no breakage. | +| Buy-now scanner | Cron 07:00 BRT | Creates `buy_opportunity` rows. Idempotent (dedup by `quote_id`). Strip the `isDemo()` short-circuit in `scan.ts`. | No-op on Anthropic error; logged + retried next run. | +| Follow-up nudges | Cron 09:00 BRT | Finds `rfq.status='sent'` with no response after N=3 days. Drafts a nudge into `agent_run.proposed_actions.draft`. Creates a `notification`. **User reviews and sends — no auto-send.** | Skip individual RFQ on draft failure; log. | +| Negotiation drafter | Triggered on `buy_opportunity` create with `status='open'` | Drafts a counter-offer linked to the opportunity. **User reviews and sends.** | Skip on draft failure; opportunity remains actionable manually. | + +Vendor-discovery agent: deferred. Discover tab keeps the curated candidate list (`DiscoverGrid.tsx`). + +### 4.5 Feature-surface hardening + +| Page | Hardening | +|---|---| +| `/digest` | Empty-state when org has 0 quotes. | +| `/inbox` | Pagination at >200 threads. Read-state via new `message_read` join table. | +| `/inbox/[vendorId]` | Mark messages read on open. Optimistic UI when sending reply. | +| `/compare` | Date-range filter (last 30/90/180 days). | +| `/vendors` | "Add vendor" + curated Discover already shipped. | +| `/vendors/[id]` | Products card already shipped. Guard against last-primary-contact deletion. | +| `/rfq/new` | Template interpolation shipped. Real send via Gmail OAuth (for email) and WhatsApp adapter (for WA). | +| `/rfq/[id]` | Status flips when vendor replies on the same thread. | +| `/opportunities` + `/opportunities/[id]` | Buy → real PO creation. Negotiate → fires negotiation agent draft. | +| `/po/[id]` | Email-the-PO button via Gmail OAuth. | +| `/insights/forecasts` | Lowered threshold already shipped. Model picker that actually saves the choice per SKU. | +| `/insights/workshop` | Cosmetic polish only. | +| `/agents` | Real `agent_run` audit log. Toggle per agent. | +| `/alerts` | Real cron-based evaluation. Trigger sends email + in-app notification. | +| `/notifications` | Mark-read endpoint. Polling for v1 (no Pusher). | +| `/settings/*` | Org settings (currency, home port), profile, integrations panel (Gmail/WhatsApp connection management), Anthropic budget. | +| `/onboarding` | First-run wizard: connect Gmail → upload chat export (optional) → tour. | +| `/ask` | Streaming Claude wired to real org-scoped data tools (already implemented in the prototype). | + +### 4.6 Logging & observability + +A first-class concern, not an afterthought. + +- **Structured JSON logs** via a single `src/lib/log.ts` wrapper. Every log line carries `{ timestamp, level, requestId, orgId, userId, route, msg, ...ctx }`. Levels: `debug | info | warn | error`. +- **Request correlation**: middleware mints a `requestId` per request, attaches to async-local-storage so any deep call site can include it. +- **Server-side context**: `currentOrg()` populates `orgId` into the ALS context — no log line should ship without it. +- **Log destinations**: Vercel Functions logs are the default. The wrapper is provider-agnostic so a Datadog/Axiom/Logflare swap is one file. +- **Key call sites with explicit logging**: + - Gmail poll cron (per-connection success/failure, page count, message count) + - WhatsApp webhook (signature verify, parsed payload shape, dropped messages) + - Extraction job tick (queue depth, jobs/sec, Anthropic latency, token cost) + - Each agent run (start, end, error, cost, rows created) + - Clerk webhook (org create, user create, errors) + - Auth failures (`currentOrg()` throw paths) +- **Sentry** for unhandled exceptions on server + client. +- **Health endpoint** `GET /api/health` returns `{ db, anthropic, lastCronRun }` for Better Stack to poll every minute. +- **Cron health page** (`/admin/cron-health`) surfaces last 7 runs of each cron job from a new `cron_run` table. + +### 4.7 Testing + +- **Unit tests** with vitest. Every new module ships with tests; target ~70 % line coverage on `src/lib/`. CI fails PRs below target. +- **Integration tests** for ingestion adapters: Gmail adapter against a recorded fixture mailbox; WhatsApp adapter against a recorded webhook payload set; both provider implementations against the shared adapter interface. +- **Multi-tenant isolation tests**: vitest helper that seeds two orgs (`orgA`, `orgB`), runs each page's data query as the other org's `currentOrg()`, and asserts no rows from the first org appear. +- **Agent tests**: each agent module is unit-tested against a fixture DB snapshot. Output draft is asserted on shape (not content). +- **CI**: GitHub Actions runs `pnpm tsc --noEmit && pnpm lint && pnpm vitest run` on every PR. +- The existing 39 prototype tests carry over to the new repo as the starting baseline. + +### 4.8 Reliability & ops (Lean SaaS bar) + +- **Sentry**: server + client SDK. Source maps via Vercel integration. Alerts → email/Slack on new issues. +- **Uptime**: Better Stack monitoring `https://app.tradyon.ai/api/health`. 1-min interval, paging webhook to email. +- **Backups**: Neon daily PITR. Manual snapshot procedure in `scripts/db-snapshot.ts`. +- **Runbook**: `docs/operations/runbook.md` covering Gmail token expired, Anthropic 429, WhatsApp signature mismatch, Clerk webhook race, cron stuck, DB connection exhaustion. Each entry has the exact diagnosis command + fix. +- **Cron health**: `cron_run { id, job_name, started_at, finished_at, status, error, ctx }` rows + the admin page mentioned above. +- **Secrets**: all in Vercel encrypted env vars. Anthropic key, Gmail client secret, WhatsApp tokens, `ADMIN_SECRET`, `GMAIL_TOKEN_KEY` marked Sensitive in Vercel UI. + +--- + +## 5. Phasing + +10–12 weeks total, ordered to de-risk early. Ingestion is the highest-risk piece; auth is the second-highest; everything else is hardening of code that already works in demo. + +| Phase | Weeks | Outcome | +|---|---|---| +| 0. Bootstrap | 1 | New repo, Vercel, Neon, Clerk, domain. CI passes on fresh clone. Logging + Sentry skeleton in place from day one. | +| 1. Auth + multi-tenancy | 1 | DEMO_MODE removed. Clerk sign-up creates org + user via webhook. Two test orgs verify isolation. Tests for both. | +| 2. Gmail ingestion | 2 | OAuth flow, history-API polling, message + quote insertion. First real supplier email shows in `/inbox`. Integration tests against fixtures. | +| 3. WhatsApp ingestion + send | 2 | Cloud (or Periskope) outbound + inbound capture. RFQ send delivers. Webhook signature verification tested. | +| 4. Extraction + agents | 1.5 | Demo extractor deleted. All 4 agents run on cron. Cost guard live. Agent tests + cost-guard tests. | +| 5. Feature-surface hardening | 2 | Pagination, read-state, real send buttons, alerts firing, real PO email, settings + integrations panel. Page-level tests. | +| 6. Ops + final polish | 1.5 | Sentry + Better Stack live. Runbook complete. Cron-health page. Security review pass (token storage, webhook signatures, cross-tenant audit). | + +--- + +## 6. Decision log (so future-Siddhant can recall) + +- **Audience**: 3–5 design partners, not public launch. +- **Self-serve Clerk sign-up**: yes; if random sign-ups become noisy during design-partner phase, add a lightweight invite-code gate (not in v1). +- **Voice notes out** for v1. +- **All 4 agents in** (vendor-discovery deferred). +- **Approach B** chosen: full prototype to prod, not just the wedge. +- **Isolation: separate repo** (Option 2). Strongest "demo never breaks" guarantee. +- **Periskope as WhatsApp fallback** if Meta Cloud verification stalls. +- **Lean SaaS ops bar** — Sentry + Better Stack + Neon backups + runbook. No on-call rotation, no SOC2. +- **Tests + logging** are first-class — must be in place from Phase 0 onward, not bolted on at the end. + +--- + +## 7. Open questions to resolve during execution + +These are not blockers for the plan but should be answered before the relevant phase starts: + +- **WhatsApp business number**: who owns the Meta Business Account? Tradyon (recommended) or each partner? Affects Phase 3. +- **Domain**: `app.tradyon.ai` is the proposal — confirm DNS access and SSL strategy with Vercel. +- **Clerk pricing tier**: free tier covers <10 k MAU; we'll be well under. Confirm at Phase 0. +- **Anthropic spend cap**: project-level monthly cap to set in Anthropic dashboard. Suggest $500/month for the design phase; revisit after first partner is live. From 2f2266c98f6b1004204438eaa3ea4d8b5ced096a Mon Sep 17 00:00:00 2001 From: Siddhant Mundra Date: Thu, 28 May 2026 11:50:18 +0530 Subject: [PATCH 2/2] docs: revise productization spec after staff-engineer review Absorbed every blocker and improvement from the review: - Split scope into Tier 1 (load-bearing, production-grade) and Tier 2 (no-regression, polish v1.1) - Gmail tokens: per-user, key_version rotation, needs_reauth UX, edge cases (historyId expiry, label scoping) - Static org_id lint rule in CI from Phase 0 (strong tenant-isolation guarantee, not just runtime) - Cron platform: 1-minute Vercel Cron (not 30s), per-org jitter to avoid Anthropic thundering herd - Moved real Gmail send (RFQ + PO) from Phase 5 into Phase 2 to remove the Phase 5 cascade risk - Onboarding wizard now its own subsection (4.5.1) with the same rigor as ingestion - Empty-state library plumbed into every Tier 1 page - Schema migration safety policy (no push in prod, Neon staging branch first, forward-compatible only) - Partner offboarding script (delete-org.ts) + supplier-side privacy policy - Agent quality_signals jsonb captured in Phase 4 even though surfaced UI is v1.1 - Re-baselined estimate to 13 weeks (12 dev + 1 buffer) for Siddhant + Claude Kept the separate-repo isolation decision per user direction. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...7-productize-tradyon-procurement-design.md | 240 +++++++++++++----- 1 file changed, 170 insertions(+), 70 deletions(-) diff --git a/docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md b/docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md index 1c67f13..57462be 100644 --- a/docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md +++ b/docs/superpowers/specs/2026-05-27-productize-tradyon-procurement-design.md @@ -1,7 +1,7 @@ # Productize Tradyon Procurement — Design Spec -**Date:** 2026-05-27 -**Author:** Siddhant Mundra + Claude (brainstorming session) +**Date:** 2026-05-27 (revised 2026-05-28 after staff-engineer review) +**Author:** Siddhant Mundra + Claude (brainstorming session, revised after review) **Status:** Approved — ready for execution planning --- @@ -14,6 +14,8 @@ Take the current Tradyon Procurement prototype (the codebase at `Sidgit11/Procur **Bar:** "Lean SaaS" reliability — partners can run real procurement on it, but no formal SLA, no SOC2. +**Engineering team:** Siddhant + Claude (Claude executes, Siddhant steers + reviews). Estimate calibrated for that pairing. + --- ## 2. Critical constraint: do not break the demo @@ -24,23 +26,58 @@ The current deployment at `procurement-manager.vercel.app` is actively used for - New repo: `TradyonProcurementProd` (private). - Old repo (`Sidgit11/ProcurementManager`) is frozen as the demo. Branch protection on `main`. README banner pointing to the new prod repo. -- New Vercel project linked to the new repo, with its own Neon Postgres DB, its own Clerk application, its own Anthropic API key, its own domain (`app.tradyon.ai` recommended). -- No code or infrastructure is shared between demo and production. Future improvements to the demo (cosmetic tweaks for sales) are ported manually. +- New Vercel project linked to the new repo, with its own Neon Postgres DB, its own Clerk application, its own Anthropic API key, its own domain (`app.tradyon.ai`). +- No code or infrastructure is shared between demo and production. +- **Cherry-pick policy:** the demo gets *no* commits from the prod repo, full stop, except a single README banner update. Future improvements to the demo (cosmetic tweaks for sales) are ported manually if and only if explicitly requested. + +The staff-engineer review flagged the porting tax of this decision (every shared component diverges, two CI configs, two dependabot streams). Acknowledged and accepted: demo stability is non-negotiable for sales. --- ## 3. Scope -### 3.1 In scope (every feature in the current prototype must work for real) - -- All app pages: digest, inbox, compare, vendors (pool + curated Discover), RFQ engine, opportunities, POs, forecasts + workshop, agents, alerts, notifications, ask, settings, vendor vault, onboarding. -- All 4 LLM agents: daily summary, buy-now scanner, follow-up nudges, negotiation drafter. (Vendor-discovery agent stays as a curated list — no shipment-data integration.) -- Real ingestion: Gmail OAuth, WhatsApp Chat Export upload (already works), WhatsApp Cloud API outbound + inbound capture (with Periskope as a swap-in fallback). -- Multi-tenant with Clerk fully on (DEMO_MODE removed). -- Lean SaaS ops: Sentry, Better Stack uptime, Neon backups, structured logs, runbook. -- Comprehensive unit + integration tests (target ~70% coverage), CI on PR. - -### 3.2 Out of scope (explicit, in writing) +The scope is split into **Tier 1** (load-bearing for daily partner use — must be production-grade) and **Tier 2** (must not regress, polish deferred to v1.1). + +### 3.1 Tier 1 — must work for daily partner use + +**Ingestion + processing** +- Gmail OAuth ingestion (per-user tokens, see §4.3). +- WhatsApp Cloud API ingestion + send (Meta verified; Periskope kept as warm-standby fallback). +- WhatsApp Chat Export upload (already works). +- Anthropic-based quote extraction (demo regex extractor removed). + +**Daily-use pages** +- `/digest` +- `/inbox` + `/inbox/[vendorId]` +- `/compare` +- `/vendors` + `/vendors/[id]` +- `/rfq/new` + `/rfq/[id]` +- `/opportunities` + `/opportunities/[id]` +- `/po/[id]` +- `/alerts` +- `/notifications` +- `/settings/*` (specifically `/settings/integrations` for managing Gmail + WhatsApp connections) +- `/onboarding` (first-run wizard — see §4.5.1) + +**Infrastructure** +- Multi-tenant with Clerk fully on (`DEMO_MODE` removed). +- 4 LLM agents in propose-only mode: daily summary, buy-now scanner, follow-up nudges, negotiation drafter. +- Lean SaaS ops: Sentry, Better Stack uptime, Neon backups + PITR, structured logs, runbook. +- Unit + integration tests (target ~70% coverage on `src/lib/`), CI on PR. +- **Static `org_id` lint rule in CI from Phase 0** (see §4.7). + +### 3.2 Tier 2 — must not break, polish deferred to v1.1 + +These pages stay in the app and continue to work, but receive only bug-fix attention during this effort. Visual polish, performance tuning, and feature gaps are explicitly deferred to v1.1. + +- `/insights/forecasts` (the page works; model-picker save is a v1.1 polish) +- `/insights/workshop` +- `/agents` (existence verified, full audit-log UI is v1.1) +- `/ask` (streaming + tool-calling works; deeper org-scoped tool coverage is v1.1) +- `/vendors` Discover tab (curated list works; shipment-data integration deferred) +- `/vendors/[id]/vault` + +### 3.3 Out of scope (explicit, in writing) - Voice-note transcription (Whisper). Voice messages show in the inbox as a link back to WhatsApp. - Outlook OAuth ingestion. @@ -49,6 +86,8 @@ The current deployment at `procurement-manager.vercel.app` is actively used for - Inbound supplier PDF OCR (PDFs go to vendor vault for manual review). - SOC2 / formal compliance. - Mobile app. +- Gmail push notifications via Pub/Sub (polling for v1; push is v1.1). +- Demo → prod data import (partners start clean — see §4.8). --- @@ -61,34 +100,53 @@ The current deployment at `procurement-manager.vercel.app` is actively used for | Git host | GitHub, new private repo | | Initial commit | Squash of `Sidgit11/ProcurementManager@98f3224` so the new repo starts clean | | Hosting | Vercel (new project) | -| DB | Neon Postgres via Vercel Marketplace (new project) | +| DB | Neon Postgres via Vercel Marketplace (new project), separate Neon project | | Auth | Clerk (new Clerk application under same Clerk org) | -| LLM | Anthropic, dedicated API key for the new project | +| LLM | Anthropic, dedicated API key for the new project, project-level monthly spend cap | | Domain | `app.tradyon.ai` (with `*.vercel.app` as bootstrap) | | Error tracking | Sentry | | Uptime | Better Stack | -| Cron | Vercel Cron (already used) | +| Cron | Vercel Cron (1-minute minimum on Pro tier; we use 1-min, never 30-sec) | +| WhatsApp primary | Meta Cloud API (already verified) | +| WhatsApp fallback | Periskope adapter (kept warm, swappable via env flag) | ### 4.2 Multi-tenancy - `DEMO_MODE=1` removed from the Vercel project. All demo-mode branches (`isDemo()`, `currentOrg()` fallback, `scan.ts` early return, `demo-extract.ts`, `upload/route.ts` synchronous tick) are deleted; the real path is the only path. - Clerk sign-up flow: user creates an account → Clerk webhook hits `/api/clerk/webhook` (signature-verified) → creates a row in `org` and `user`. The current webhook handler exists but is stubbed; needs real implementation. -- Every server-rendered page already calls `currentOrg()`. As part of bootstrap, an audit pass walks every `db.select()` to confirm it filters by `org_id`. Coverage backed by a vitest helper that exercises each page from two test orgs and asserts no cross-tenant rows appear. - Schema is already multi-tenant from day one (every business table has `org_id`). +- **Runtime check (necessary, not sufficient):** vitest helper that seeds two orgs (`orgA`, `orgB`), runs each Tier 1 page's data query as the other org's `currentOrg()`, asserts no rows from the first org appear. +- **Static check (the strong guarantee):** CI job runs an ast-grep / ESLint rule that fails any `db.select()`, `db.update()`, `db.delete()`, or `db.execute(sql\`…\`)` call whose surrounding query does not include an `org_id` filter or an explicit `// org-scoped: ` annotation. Lives in Phase 0 so every PR from Phase 1 onward is guarded. ### 4.3 Ingestion -**Gmail OAuth** -- New table `gmail_connection { id, org_id, user_id, email, access_token_enc, refresh_token_enc, history_id, scopes[], status, last_synced_at }`. -- Encryption: libsodium with a project-scoped secret in Vercel env (`GMAIL_TOKEN_KEY`). One key per project — rotation is a manual procedure documented in the runbook. +**Gmail OAuth (per-user tokens)** + +Token scoping decision: **per-user**. Each teammate connects their own Gmail. RFQs sent from a user's Gmail show `From: their.address@…`. When a user leaves the org, their `gmail_connection` row is preserved (thread history stays linked to the org) but RFQ-send-as-them is disabled. + +- New table: + ``` + gmail_connection { + id, org_id, user_id, email, + access_token_enc, refresh_token_enc, key_version, + history_id, scopes[], + status, -- "active" | "needs_reauth" | "revoked" + last_synced_at, + last_error, last_error_at + } + ``` +- Encryption: libsodium with project-scoped secret `GMAIL_TOKEN_KEY` in Vercel env, marked Sensitive. +- **Key rotation procedure (documented in runbook):** generate `GMAIL_TOKEN_KEY_v2`, deploy with both keys readable, mark new connections as `key_version=2`, lazy-re-encrypt v1 rows on next access, remove v1 key after the longest possible re-auth window (90 days). `key_version` column makes the dual-read window safe. +- **Refresh-token failure UX:** when Google invalidates the refresh token (silent after 6 months inactivity or password change), the poller catches `invalid_grant`, sets `status='needs_reauth'`, creates an in-app notification, and surfaces a yellow banner on every page until reconnected. RFQ-send button disabled with clear copy: *"Reconnect Gmail to send."* - New endpoints: `POST /api/auth/gmail/start`, `GET /api/auth/gmail/callback`, `POST /api/auth/gmail/disconnect`. Connect button lives in `/settings/integrations`. -- Pull strategy: Vercel Cron every 5 minutes runs `/api/cron/gmail-poll`. Per active connection, calls Gmail History API with `historyId`. Upserts new `message` + `thread` rows. Enqueues `extraction_job` for each new message. +- Pull strategy: Vercel Cron every 1 minute runs `/api/cron/gmail-poll`. Per active connection, calls Gmail History API with `historyId`. Upserts new `message` + `thread` rows. Enqueues `extraction_job` for each new message. +- **Edge cases handled:** `historyId` expiry (>1 week stale → full INBOX rescan, bounded 90 days), label scoping (only INBOX + `quoted-replies` labels, configurable), partial-sync resumption, dedup by Gmail `message-id` header. - First connect does a bounded 90-day historical sync. -- Outbound: RFQ send routes through the user's Gmail (uses the OAuth token, so `From:` is the user's own address). +- Outbound: RFQ send routes through the user's Gmail OAuth token. **WhatsApp** -- Env-gated provider: `WHATSAPP_PROVIDER=cloud|periskope`. Cloud is the target; Periskope is a fallback if Meta verification stalls. -- New adapter interface: `WhatsappAdapter { sendMessage(input), verifyWebhook(req), parseWebhook(req) }`. Both Cloud and Periskope implementations live behind it. +- Env-gated provider: `WHATSAPP_PROVIDER=cloud|periskope`. Cloud is the production path (already verified). Periskope is kept implemented and warm so we can flip with one env-var change if Meta has an outage or policy change. +- Adapter interface: `WhatsappAdapter { sendMessage(input), verifyWebhook(req), parseWebhook(req) }`. Both Cloud and Periskope implementations live behind it. Same integration tests run against both. - Webhook: `POST /api/webhooks/whatsapp`. Signature verified per provider. - Inbound messages upserted to `message` + `thread`, extraction enqueued. - Outbound: RFQ + nudge messages sent via the active provider. @@ -97,7 +155,8 @@ The current deployment at `procurement-manager.vercel.app` is actively used for **Extraction** - `demo-extract.ts` deleted. Anthropic-based `extract.ts` (already exists in the prototype) becomes the only path. - Cost guard: per-org daily budget in `org.settings.anthropicDailyBudgetUsd` (default $5). Soft-fail (queue + warn the org owner via in-app notification) when exceeded. -- Job runner: `extraction_job` table exists. Move from inline `tickOnce(200)` (the demo upload pattern) to a background cron `/api/cron/extraction-tick` every 30 s. +- Job runner: `extraction_job` table exists. Move from inline `tickOnce(200)` (the demo upload pattern) to a background cron `/api/cron/extraction-tick` every 1 minute (Vercel Pro minimum). +- **Cron platform reality:** Vercel Cron minimum is 1 minute, not 30 seconds. Accepted 1-minute extraction latency. If sub-minute latency becomes a partner ask, migrate to QStash or Inngest in v1.1. ### 4.4 Agents @@ -105,35 +164,53 @@ All four agents run in **propose-only mode for v1** — the `auto_execute` colum | Agent | Trigger | Output | Failure mode | |---|---|---|---| -| Daily summary | Cron 06:00 BRT per org | Writes `agent_run.proposed_actions.summary` rendered on `/digest` hero. Uses Claude Haiku for cost. | UI falls back to the existing computed fallback string — no breakage. | -| Buy-now scanner | Cron 07:00 BRT | Creates `buy_opportunity` rows. Idempotent (dedup by `quote_id`). Strip the `isDemo()` short-circuit in `scan.ts`. | No-op on Anthropic error; logged + retried next run. | -| Follow-up nudges | Cron 09:00 BRT | Finds `rfq.status='sent'` with no response after N=3 days. Drafts a nudge into `agent_run.proposed_actions.draft`. Creates a `notification`. **User reviews and sends — no auto-send.** | Skip individual RFQ on draft failure; log. | +| Daily summary | Cron 06:00 BRT per org, **jittered by `hash(org_id) mod 30 min`** | Writes `agent_run.proposed_actions.summary` rendered on `/digest` hero. Uses Claude Haiku for cost. | UI falls back to the existing computed fallback string — no breakage. | +| Buy-now scanner | Cron 07:00 BRT per org, jittered | Creates `buy_opportunity` rows. Idempotent (dedup by `quote_id`). Strip the `isDemo()` short-circuit in `scan.ts`. | No-op on Anthropic error; logged + retried next run. | +| Follow-up nudges | Cron 09:00 BRT per org, jittered | Finds `rfq.status='sent'` with no response after N=3 days. Drafts a nudge into `agent_run.proposed_actions.draft`. Creates a `notification`. **User reviews and sends — no auto-send.** | Skip individual RFQ on draft failure; log. | | Negotiation drafter | Triggered on `buy_opportunity` create with `status='open'` | Drafts a counter-offer linked to the opportunity. **User reviews and sends.** | Skip on draft failure; opportunity remains actionable manually. | +**Cron jitter:** without staggering, 5 orgs all firing at 06:00 BRT will hit Anthropic rate limits as a thundering herd. Each cron's effective fire time is `nominal + (hash(org_id) % 30 minutes)`. Implemented as a leader cron that schedules per-org work. + +**Agent quality signals (new):** add `agent_run.quality_signals jsonb` capturing `{ draft_sent: bool, draft_edited_pct: number, time_to_send_seconds: number, user_dismissed: bool }`. This is the only objective signal that the agents are useful. Surfaced on the `/agents` Tier 2 page as a v1.1 polish item, but the *data capture* lands in Phase 4. + Vendor-discovery agent: deferred. Discover tab keeps the curated candidate list (`DiscoverGrid.tsx`). -### 4.5 Feature-surface hardening +### 4.5 Feature-surface hardening (Tier 1) | Page | Hardening | |---|---| -| `/digest` | Empty-state when org has 0 quotes. | -| `/inbox` | Pagination at >200 threads. Read-state via new `message_read` join table. | +| `/digest` | Empty-state when org has 0 quotes ("we're listening — first quotes appear here within minutes of connecting Gmail"). | +| `/inbox` | Pagination at >200 threads. Read-state via new `message_read` join table. Empty-state. | | `/inbox/[vendorId]` | Mark messages read on open. Optimistic UI when sending reply. | -| `/compare` | Date-range filter (last 30/90/180 days). | -| `/vendors` | "Add vendor" + curated Discover already shipped. | +| `/compare` | Date-range filter (last 30/90/180 days). Empty-state per SKU. | +| `/vendors` | "Add vendor" + curated Discover already shipped. Empty-state on My Pool. | | `/vendors/[id]` | Products card already shipped. Guard against last-primary-contact deletion. | -| `/rfq/new` | Template interpolation shipped. Real send via Gmail OAuth (for email) and WhatsApp adapter (for WA). | -| `/rfq/[id]` | Status flips when vendor replies on the same thread. | +| `/rfq/new` | Template interpolation shipped. **Real send via Gmail OAuth (for email) and WhatsApp adapter (for WA) shipped in Phase 2 + 3, not Phase 5.** | +| `/rfq/[id]` | Status flips when vendor replies on the same thread (matched via `In-Reply-To` header + thread-id). | | `/opportunities` + `/opportunities/[id]` | Buy → real PO creation. Negotiate → fires negotiation agent draft. | -| `/po/[id]` | Email-the-PO button via Gmail OAuth. | -| `/insights/forecasts` | Lowered threshold already shipped. Model picker that actually saves the choice per SKU. | -| `/insights/workshop` | Cosmetic polish only. | -| `/agents` | Real `agent_run` audit log. Toggle per agent. | +| `/po/[id]` | **Email-the-PO button via Gmail OAuth ships in Phase 2** (with the rest of Gmail send), not Phase 5. | | `/alerts` | Real cron-based evaluation. Trigger sends email + in-app notification. | | `/notifications` | Mark-read endpoint. Polling for v1 (no Pusher). | -| `/settings/*` | Org settings (currency, home port), profile, integrations panel (Gmail/WhatsApp connection management), Anthropic budget. | -| `/onboarding` | First-run wizard: connect Gmail → upload chat export (optional) → tour. | -| `/ask` | Streaming Claude wired to real org-scoped data tools (already implemented in the prototype). | +| `/settings/*` | Org settings (currency, home port), profile, **integrations panel (Gmail per-user + WhatsApp org-level connection management)**, Anthropic budget. | + +### 4.5.1 Onboarding wizard (hardened, "make-or-break") + +The first-run experience determines whether 3–5 design partners stick. It gets the same hardening rigor as ingestion. + +**Steps** +1. **Welcome** — org name confirmation (came from Clerk sign-up), explainer of what happens next. +2. **Connect Gmail** — single-button OAuth, clear permission scope explainer (we only read INBOX + send-as), retry on failure with explicit error copy. +3. **Connect WhatsApp** *(optional)* — choose: Cloud API number (Tradyon-issued) or upload existing Chat Export. Chat Export sample download link. +4. **Org defaults** — currency (default USD), home port (default `BR-NVT`), preferred channels. +5. **Catalog confirmation** — present the 7 Polico-default SKUs, partner can keep or replace with their own SKUs. +6. **Tour** — 4-step interactive walkthrough of `/digest`, `/inbox`, `/compare`, `/rfq/new` with empty-state copy at every step. "We're listening — first quotes appear here within minutes." + +**State management** +- `org.settings.onboardingStep` persists current step. Partners can resume. +- Each step writes a row to `event_log` for funnel analysis. +- Skip-and-come-back is allowed for every step except step 2 (Gmail). Step 2 can be deferred to step 6, but the tour explicitly tells them they'll see nothing until Gmail is connected. + +**Empty states for every Tier 1 page** when org has no data: same copy library, accessible via `` component. Reasons: `no-quotes-yet`, `gmail-not-connected`, `no-opportunities-yet`, `no-rfqs-yet`, `no-alerts-yet`. ### 4.6 Logging & observability @@ -144,12 +221,13 @@ A first-class concern, not an afterthought. - **Server-side context**: `currentOrg()` populates `orgId` into the ALS context — no log line should ship without it. - **Log destinations**: Vercel Functions logs are the default. The wrapper is provider-agnostic so a Datadog/Axiom/Logflare swap is one file. - **Key call sites with explicit logging**: - - Gmail poll cron (per-connection success/failure, page count, message count) + - Gmail poll cron (per-connection success/failure, page count, message count, `historyId` advance) - WhatsApp webhook (signature verify, parsed payload shape, dropped messages) - Extraction job tick (queue depth, jobs/sec, Anthropic latency, token cost) - - Each agent run (start, end, error, cost, rows created) + - Each agent run (start, end, error, cost, rows created, **`quality_signals`**) - Clerk webhook (org create, user create, errors) - Auth failures (`currentOrg()` throw paths) + - **Key rotation events** (`GMAIL_TOKEN_KEY` re-encrypt operations) - **Sentry** for unhandled exceptions on server + client. - **Health endpoint** `GET /api/health` returns `{ db, anthropic, lastCronRun }` for Better Stack to poll every minute. - **Cron health page** (`/admin/cron-health`) surfaces last 7 runs of each cron job from a new `cron_run` table. @@ -157,50 +235,71 @@ A first-class concern, not an afterthought. ### 4.7 Testing - **Unit tests** with vitest. Every new module ships with tests; target ~70 % line coverage on `src/lib/`. CI fails PRs below target. -- **Integration tests** for ingestion adapters: Gmail adapter against a recorded fixture mailbox; WhatsApp adapter against a recorded webhook payload set; both provider implementations against the shared adapter interface. -- **Multi-tenant isolation tests**: vitest helper that seeds two orgs (`orgA`, `orgB`), runs each page's data query as the other org's `currentOrg()`, and asserts no rows from the first org appear. -- **Agent tests**: each agent module is unit-tested against a fixture DB snapshot. Output draft is asserted on shape (not content). -- **CI**: GitHub Actions runs `pnpm tsc --noEmit && pnpm lint && pnpm vitest run` on every PR. +- **Integration tests** for ingestion adapters: Gmail adapter against a recorded fixture mailbox; WhatsApp adapter against a recorded webhook payload set; both provider implementations (Cloud + Periskope) against the shared adapter interface. +- **Multi-tenant isolation tests (runtime):** vitest helper that seeds two orgs (`orgA`, `orgB`), runs each Tier 1 page's data query as the other org's `currentOrg()`, asserts no rows from the first org appear. +- **Multi-tenant isolation guard (static, in CI from Phase 0):** ast-grep or ESLint rule that fails any `db.select()/db.update()/db.delete()/db.execute(sql\`…\`)` call whose surrounding query lacks an `org_id` filter or an explicit `// org-scoped: ` annotation. This is the strong guarantee. +- **Agent tests**: each agent module is unit-tested against a fixture DB snapshot. Output draft is asserted on shape (not content). `quality_signals` write paths tested. +- **CI**: GitHub Actions runs `pnpm tsc --noEmit && pnpm lint && pnpm vitest run` on every PR. Coverage gate enforced. - The existing 39 prototype tests carry over to the new repo as the starting baseline. -### 4.8 Reliability & ops (Lean SaaS bar) +### 4.8 Reliability, security & ops (Lean SaaS bar) - **Sentry**: server + client SDK. Source maps via Vercel integration. Alerts → email/Slack on new issues. - **Uptime**: Better Stack monitoring `https://app.tradyon.ai/api/health`. 1-min interval, paging webhook to email. -- **Backups**: Neon daily PITR. Manual snapshot procedure in `scripts/db-snapshot.ts`. -- **Runbook**: `docs/operations/runbook.md` covering Gmail token expired, Anthropic 429, WhatsApp signature mismatch, Clerk webhook race, cron stuck, DB connection exhaustion. Each entry has the exact diagnosis command + fix. +- **Backups**: Neon daily PITR enabled. Manual snapshot procedure in `scripts/db-snapshot.ts`. +- **Runbook**: `docs/operations/runbook.md` covering Gmail token expired/invalid_grant, Anthropic 429, WhatsApp signature mismatch, Clerk webhook race, cron stuck, DB connection exhaustion, `GMAIL_TOKEN_KEY` rotation procedure. Each entry has the exact diagnosis command + fix. - **Cron health**: `cron_run { id, job_name, started_at, finished_at, status, error, ctx }` rows + the admin page mentioned above. - **Secrets**: all in Vercel encrypted env vars. Anthropic key, Gmail client secret, WhatsApp tokens, `ADMIN_SECRET`, `GMAIL_TOKEN_KEY` marked Sensitive in Vercel UI. +- **Schema migration safety policy** *(new)*: + - Drizzle migrations are forward-compatible only — no destructive renames, no required-column adds without a default. + - `drizzle-kit push` is forbidden in prod; only `drizzle-kit migrate` against a Neon staging branch first. + - Every migration is tested on a Neon branch that's a fresh fork of prod data before being applied to prod. + - Rollback policy: every migration has a documented "how to undo" comment in the migration file. If undoing is destructive, the migration is rewritten as additive. +- **Partner offboarding / data deletion *(new)*:** `scripts/delete-org.ts` script. Takes an `org_id`, deletes from every business table via cascade. Preserves `event_log` rows (anonymized) for compliance audit. Documented in runbook. +- **Supplier-side privacy *(new)*:** ingested supplier emails contain third-party PII. The privacy policy on `app.tradyon.ai/legal/privacy` explicitly names this, the lawful basis (legitimate interest), and the retention policy (delete on partner offboarding via the script above). One-page legal doc, drafted in Phase 6. --- ## 5. Phasing -10–12 weeks total, ordered to de-risk early. Ingestion is the highest-risk piece; auth is the second-highest; everything else is hardening of code that already works in demo. +**Total: 13 weeks** (12 weeks of dev + 1 week buffer), Siddhant + Claude. + +Estimate revised up from the original 10–12 weeks after staff-engineer review: +- Gmail OAuth + History API edge cases (Phase 2): 2 → 3 weeks +- WhatsApp dev work shorter because Meta verification is already done +- Phase 5 shorter because "real send" moved into Phases 2 + 3 +- Phase 1 grew slightly to absorb the Clerk webhook + static-isolation work +- 1 week of buffer at the end for things that always slip | Phase | Weeks | Outcome | |---|---|---| -| 0. Bootstrap | 1 | New repo, Vercel, Neon, Clerk, domain. CI passes on fresh clone. Logging + Sentry skeleton in place from day one. | -| 1. Auth + multi-tenancy | 1 | DEMO_MODE removed. Clerk sign-up creates org + user via webhook. Two test orgs verify isolation. Tests for both. | -| 2. Gmail ingestion | 2 | OAuth flow, history-API polling, message + quote insertion. First real supplier email shows in `/inbox`. Integration tests against fixtures. | -| 3. WhatsApp ingestion + send | 2 | Cloud (or Periskope) outbound + inbound capture. RFQ send delivers. Webhook signature verification tested. | -| 4. Extraction + agents | 1.5 | Demo extractor deleted. All 4 agents run on cron. Cost guard live. Agent tests + cost-guard tests. | -| 5. Feature-surface hardening | 2 | Pagination, read-state, real send buttons, alerts firing, real PO email, settings + integrations panel. Page-level tests. | -| 6. Ops + final polish | 1.5 | Sentry + Better Stack live. Runbook complete. Cron-health page. Security review pass (token storage, webhook signatures, cross-tenant audit). | +| 0. Bootstrap + isolation safety | 1.5 | New repo, Vercel, Neon, Clerk, domain. CI passes. Logging + Sentry skeleton in place from day one. **Static `org_id` lint rule in CI.** WhatsApp Cloud verification confirmed (done). Periskope adapter scaffold landed as warm fallback. Domain DNS + SSL working. | +| 1. Auth + multi-tenancy | 1.5 | DEMO_MODE removed. Clerk sign-up creates org + user via signature-verified webhook. Two test orgs verify isolation (runtime + static checks). Empty-state component library landed. | +| 2. Gmail ingestion + send | 3 | Per-user OAuth flow with `key_version` + rotation procedure. History-API polling (1-min cron). Message + quote insertion. `needs_reauth` banner UX. **`/rfq/new` and `/po/[id]` real Gmail send shipped in this phase.** Integration tests against fixture mailbox. | +| 3. WhatsApp ingestion + send | 2 | Cloud adapter (primary, already verified) + Periskope adapter (fallback) both implemented behind shared interface. Webhook signature verification tested. RFQ + nudge send delivers. Inbound capture + extraction enqueue working. | +| 4. Extraction + agents | 2 | Demo extractor deleted. All 4 agents run on 1-min cron platform with per-org jitter. Cost guard live. `agent_run.quality_signals` data capture wired. Agent tests against fixtures. | +| 5. Tier 1 feature-surface hardening | 1.5 | Pagination, read-state, alerts firing, **hardened onboarding wizard with empty-state library plumbed into every Tier 1 page**. Tier 2 pages get smoke-tested for no-regression only. | +| 6. Ops + final polish | 1.5 | Sentry + Better Stack live. Runbook complete. Cron-health page. `delete-org.ts` script. Schema migration policy documented + tested. Privacy policy page. Security review pass (token storage, webhook signatures, cross-tenant audit). | --- -## 6. Decision log (so future-Siddhant can recall) +## 6. Decision log - **Audience**: 3–5 design partners, not public launch. - **Self-serve Clerk sign-up**: yes; if random sign-ups become noisy during design-partner phase, add a lightweight invite-code gate (not in v1). - **Voice notes out** for v1. - **All 4 agents in** (vendor-discovery deferred). -- **Approach B** chosen: full prototype to prod, not just the wedge. -- **Isolation: separate repo** (Option 2). Strongest "demo never breaks" guarantee. -- **Periskope as WhatsApp fallback** if Meta Cloud verification stalls. -- **Lean SaaS ops bar** — Sentry + Better Stack + Neon backups + runbook. No on-call rotation, no SOC2. -- **Tests + logging** are first-class — must be in place from Phase 0 onward, not bolted on at the end. +- **Approach B** chosen: full prototype to prod, but split into **Tier 1 (production-grade) and Tier 2 (no-regression)**. +- **Isolation: separate repo** (Option 2). Strongest "demo never breaks" guarantee. Porting tax accepted. +- **Periskope as WhatsApp fallback** — kept warm, swappable via env flag. Meta Cloud is the primary path (already verified). +- **Gmail token scoping**: per-user. `key_version` column for rotation. `needs_reauth` UX for refresh-token invalidation. +- **Cron platform**: Vercel Cron 1-minute minimum. No 30-second loops. Cron jitter per-org via `hash(org_id) mod 30 min` to avoid Anthropic thundering herd. +- **Static `org_id` lint check** in CI from Phase 0. Runtime cross-tenant tests are necessary but not sufficient. +- **Schema migrations**: forward-compatible only, no `drizzle-kit push` in prod, Neon staging branch first. +- **Partners start clean**: no demo → prod data import. Demo data is for sales, not for partner workspaces. +- **Engineering**: Siddhant + Claude. Estimate baselined at 13 weeks. +- **Onboarding wizard** gets the same hardening rigor as ingestion (§4.5.1). +- **Agent quality signals**: `agent_run.quality_signals jsonb` data capture lands in Phase 4 even though the surfaced UI is v1.1. --- @@ -208,7 +307,8 @@ A first-class concern, not an afterthought. These are not blockers for the plan but should be answered before the relevant phase starts: -- **WhatsApp business number**: who owns the Meta Business Account? Tradyon (recommended) or each partner? Affects Phase 3. -- **Domain**: `app.tradyon.ai` is the proposal — confirm DNS access and SSL strategy with Vercel. -- **Clerk pricing tier**: free tier covers <10 k MAU; we'll be well under. Confirm at Phase 0. -- **Anthropic spend cap**: project-level monthly cap to set in Anthropic dashboard. Suggest $500/month for the design phase; revisit after first partner is live. +- **Domain**: `app.tradyon.ai` — confirm DNS access and SSL strategy with Vercel during Phase 0. +- **Clerk pricing tier**: free tier covers <10k MAU; we'll be well under. Confirm at Phase 0. +- **Anthropic spend cap**: project-level monthly cap. Suggest $500/month for the design phase; revisit after first partner is live. +- **Privacy policy hosting**: confirm Tradyon legal team has a template, or draft from scratch in Phase 6. +- **Onboarding teammate invites**: partners can invite teammates via Clerk's org-invite UI — confirm Clerk's free-tier supports org invites or budget for the paid tier.