From 9c86c181a3d9ec808fc7e5de9ff74a5d410ae9ff Mon Sep 17 00:00:00 2001 From: scotton Date: Sat, 2 May 2026 12:46:00 -0500 Subject: [PATCH 1/3] docs: capture M8 live-deploy lessons learned + Path A demo proven MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records what actually happened during the live deploy on May 2, 2026, so the next person doing a fresh setup doesn't re-discover the same gotchas. m8-live-demo-checklist.md: - Mark Path A demo items as completed; Vapi item flagged as Path B deferral - New "Lessons learned" section covering: - Three parallel Stripe environments (live / legacy test / sandbox); use Stripe Workbench Shell to register webhooks in the right env - CORS preflight handling missing from _shared/http.ts (M6 was Vapi-only); fixed by adding handlePreflight() and wiring it in admin_create_manual_order - Auth init bootstrap pattern (PR #1) — getSession() then onAuthStateChange skipping INITIAL_SESSION - Stripe success_url 404 on the marketing site (open issue) - Path B (Vapi voice integration) deferred — vapi_call_start response shape doesn't match Vapi's assistant-request contract project-status.md: - Update Current Status header to reflect Path A demo proven live - Recently Completed: detailed M8 entry covering cloud Supabase provision, Stripe Connect, Telnyx 10DLC, demo path, and the two ship-blockers found and fixed (CORS, auth init) - Current Focus: M8 fast-follows (success_url fix, Path B) before M9 Co-Authored-By: Claude Opus 4.7 (1M context) --- developer/m8-live-demo-checklist.md | 80 +++++++++++++++++++++++++---- docs/project-status.md | 16 +++--- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/developer/m8-live-demo-checklist.md b/developer/m8-live-demo-checklist.md index e19e144..e60117b 100644 --- a/developer/m8-live-demo-checklist.md +++ b/developer/m8-live-demo-checklist.md @@ -257,14 +257,74 @@ With everything wired, run the full path: All items below must be checked before M8 is marked fully complete: -- [ ] Cloud Supabase project provisioned; all 6 migrations applied; seed data present -- [ ] All Edge Function secrets set (`STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `TELNYX_API_KEY`, `TELNYX_FROM_NUMBER` and/or `TELNYX_MESSAGING_PROFILE_ID`) -- [ ] GitHub Actions secrets set (6 values) -- [ ] `deploy.yml` workflow ran green; admin + kitchen + Edge Functions all deployed -- [ ] `admin.dialtone.menu` and `kitchen.dialtone.menu` load over HTTPS with valid TLS -- [ ] Stripe webhook registered; `STRIPE_WEBHOOK_SECRET` updated and functions re-deployed -- [ ] Stripe Connect platform account active; Sui's Sushi connected account wired -- [ ] Telnyx DID provisioned (`+16296001047`); 10DLC brand + campaign submitted; messaging profile created and attached -- [ ] Vapi assistant created; all tool webhooks pointing to cloud Edge Function URLs; phone number assigned -- [ ] Full demo path executed end-to-end (voice → order → SMS → Stripe payment → kitchen board) +- [x] Cloud Supabase project provisioned; all 6 migrations applied; seed data present (project `klzznfagrtormretqsgb`) +- [x] All Edge Function secrets set (`STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `TELNYX_API_KEY`, `TELNYX_FROM_NUMBER`) +- [x] GitHub Actions secrets set (6 values) +- [x] `deploy.yml` workflow ran green; admin + kitchen + Edge Functions all deployed +- [x] `admin.dialtone.menu` and `kitchen.dialtone.menu` load over HTTPS with valid TLS +- [x] Stripe webhook registered; `STRIPE_WEBHOOK_SECRET` set and functions re-deployed +- [x] Stripe Connect platform account active; Sui's Sushi connected account wired (`acct_1TSRbH2ME93XgAX1`) +- [x] Telnyx DID `+16296001047` provisioned and Active; 10DLC brand verified (`B0YDBOE`); campaign Active (`CNZVYD6`); messaging profile created and attached +- [ ] Vapi assistant created; all tool webhooks pointing to cloud Edge Function URLs; phone number assigned (deferred — see Path B note below) +- [x] **Path A demo** executed end-to-end: admin order → Telnyx SMS → Stripe Checkout → webhook → kitchen Realtime board update → tap-advance flow +- [ ] Full **voice** demo path (Vapi inbound call → order → SMS → kitchen) — deferred to Path B fast-follow - [ ] All three failure modes validated (abandoned, declined, replay) + +--- + +## Lessons learned (May 2, 2026) + +Things that bit during the live deploy and aren't obvious from M7's local-stack experience. **Read this before doing the next live deploy** (e.g. when onboarding a second restaurant or spinning up a staging environment). + +### Stripe environment proliferation + +Modern Stripe has **three** parallel environments per account, each with its own API keys, webhooks, and event streams that do **not** cross-pollinate: + +1. **Live mode** — real money, accessed via `dashboard.stripe.com/dashboard` +2. **Legacy test mode** — accessed via `dashboard.stripe.com/test/...` URLs +3. **Sandboxes** — accessed via Workbench, isolated workspaces (each with its own keys) + +If your `STRIPE_SECRET_KEY` is from one env and your webhook is in another, events fire in the key's env and never reach the webhook — **silently**, with `pending_webhooks: 0` on the events. Symptom: payments succeed in Stripe Checkout but the order stays at `pending_payment` forever. + +**Resolution pattern that worked:** register the webhook **via Stripe Workbench Shell** instead of the dashboard UI. The Shell's CLI runs in whatever env you're currently viewing — guarantees env match. Run: + +```bash +stripe webhook_endpoints create \ + --url "https://.supabase.co/functions/v1/stripe_webhook" \ + --enabled-events "checkout.session.completed" \ + --enabled-events "checkout.session.expired" \ + --enabled-events "payment_intent.payment_failed" +``` + +Stripe returns the new webhook with its signing secret in the response. That signing secret pairs with the keys in the same Workbench env. + +### CORS preflight on browser-called Edge Functions + +M6 designed `_shared/http.ts` for Vapi-only server-to-server traffic and explicitly omitted CORS handling. M7 added `admin_create_manual_order` which is called from the admin SPA — browsers send a CORS preflight OPTIONS that the function didn't answer, blocking the actual POST. Symptom: admin "Create & send SMS link" button never returns; Edge Function logs show no invocation. + +**Resolution:** added `corsHeaders` + `handlePreflight()` in `_shared/http.ts`; `admin_create_manual_order` calls `handlePreflight(req)` before any other logic. Future browser-callable Edge Functions need the same call. + +### Auth init bootstrap + +Both apps relied on `onAuthStateChange` firing `INITIAL_SESSION` on mount to flip the loading state. In some browser/SDK combinations a restored session never fires the event, leaving the spinner stuck forever after a refresh. Symptom: `admin.dialtone.menu` and `kitchen.dialtone.menu` showed "Loading..." indefinitely after a page refresh, despite all REST + WebSocket requests returning 200. + +**Resolution (PR #1):** bootstrap the session from `supabase.auth.getSession()` synchronously on mount; subscribe to `onAuthStateChange` for subsequent events but early-return on `INITIAL_SESSION` so we don't double-fire DB queries. Pattern lives in `apps/admin/src/lib/auth-context.tsx` and `apps/kitchen/src/app.tsx`. + +### Stripe Checkout success_url 404 + +`admin_create_manual_order` builds `success_url = ${DIALTONE_PUBLIC_BASE_URL}/orders/${orderId}/paid`. Default `DIALTONE_PUBLIC_BASE_URL` is `https://dialtone.menu` — the **marketing site**, which has no `/orders/...` route. Symptom: customer pays successfully, then lands on the marketing site's 404 page. The order still flips to `paid` (webhook is independent), but the customer-facing experience is broken. + +**Open issue.** Two options for the fix: + +- Set `DIALTONE_PUBLIC_BASE_URL=https://admin.dialtone.menu` and add a `/orders/:id/paid` route to the admin app showing a "thank you" view. Simplest and uses an existing app. +- Add a `/orders/:id/paid` route to the marketing site (sibling `dialtone_menu/` repo). Better customer UX but needs cross-repo coordination. + +Either way, set `DIALTONE_PUBLIC_BASE_URL` in cloud Supabase secrets and ship the matching route. **Do not ship to a real customer with the 404 redirect in place.** + +### Vapi voice integration deferred (Path B) + +`vapi_call_start` returns its own JSON shape `{ status, prompt, tools, first_message, context }` that does **not** match Vapi's expected `assistant-request` response format `{ assistant: {...} }`. Additionally, Vapi sends multiple lifecycle event types (`call-start`, `end-of-call-report`, `function-call`, `status-update`) to a single server URL — our function only handles `call-start`-shaped requests. + +The 60 integration tests pass against our own request/response shape, not Vapi's. **The Vapi/Telnyx voice path was never live-tested.** + +For the M8 demo, voice was deferred ("Path A": admin manual order flow proves the same end-to-end plumbing minus the voice front-end). Path B is real engineering work — rewrite `vapi_call_start` to return Vapi-compliant assistant-request responses, add `message.type` dispatch for end-of-call, update integration tests against Vapi's actual webhook contract. Estimated 4–6 hours of focused work. diff --git a/docs/project-status.md b/docs/project-status.md index 5a7dccb..fef2203 100644 --- a/docs/project-status.md +++ b/docs/project-status.md @@ -1,8 +1,8 @@ # DialTone - Project Status **Project Start:** April 17, 2026 -**Last Update:** May 1, 2026 -**Current Status:** M8 code complete + SMS provider swapped from Twilio to Telnyx. Operational live-vendor validation pending (see `github/ISSUES/008-m8-end-to-end-demo.md`). +**Last Update:** May 2, 2026 +**Current Status:** M8 **Path A demo proven end-to-end live** — admin manual order → Telnyx SMS → Stripe Checkout → webhook → kitchen Realtime board. Path B (Vapi voice integration) deferred — see lessons-learned in `developer/m8-live-demo-checklist.md`. Two known issues open: Stripe success_url returns 404 on the marketing site (set `DIALTONE_PUBLIC_BASE_URL` + add success route); Vapi response shape rewrite needed before voice can be wired live. **Milestones Complete:** 8 of 12 **Test Suite:** 188 unit tests + 68 integration tests green (`pnpm ci:fast` passes; `pnpm ci:full` full-lane passes against live Supabase + functions stack) **Dependencies:** pnpm workspace installed and locked (`pnpm-lock.yaml` present) @@ -30,16 +30,16 @@ ## Current Focus -M9 is next: Reservations — voice reservation booking (`check_availability`, `create_reservation`, `cancel_reservation`), anti-double-booking transactional lock, reservation management views in admin, confirmation + reminder SMS. +**M8 fast-follows** (block voice and customer-facing pickup but not the demo): -Pending operational steps from M8 (blocked on vendor accounts — see `github/ISSUES/008-m8-end-to-end-demo.md`): -- Cloud Supabase project provisioned + migrations applied + Edge Function secrets set -- Stripe Connect platform account + Telnyx 10DLC brand/campaign/messaging-profile + Vapi assistant wired to cloud URLs -- Custom domains `admin.dialtone.menu` + `kitchen.dialtone.menu` bound in Cloudflare Workers dashboard -- GitHub Actions secrets set; `workflow_dispatch` on `deploy.yml` triggered +1. **Stripe success_url 404 fix.** `admin_create_manual_order` builds `success_url = ${DIALTONE_PUBLIC_BASE_URL}/orders/${orderId}/paid` and the default base is `https://dialtone.menu` (marketing site, no such route). Set `DIALTONE_PUBLIC_BASE_URL=https://admin.dialtone.menu` AND add a `/orders/:id/paid` route to the admin app (or add the route to the marketing site repo). Webhook fires regardless, so the order still flips to paid; only the customer-facing "thank you" page is broken. +2. **Path B — Vapi voice integration.** `vapi_call_start` returns a custom JSON shape that doesn't match Vapi's `assistant-request` contract; lifecycle events (`call-start`, `end-of-call-report`, etc.) need `message.type` dispatch. Estimated 4–6 hours of focused work to rewrite the response shape, add the dispatch, and update integration tests against Vapi's actual webhook schema. Until done, voice path is not live. + +After fast-follows, M9 is next: Reservations — voice reservation booking (`check_availability`, `create_reservation`, `cancel_reservation`), anti-double-booking transactional lock, reservation management views in admin, confirmation + reminder SMS. ## Recently Completed +- **M8 Path A demo proven live** (May 2, 2026). Cloud Supabase project `klzznfagrtormretqsgb` provisioned, migrations + seed applied. Both apps deployed at `admin.dialtone.menu` and `kitchen.dialtone.menu` with TLS. Stripe Connect platform active; Sui's Sushi connected account `acct_1TSRbH2ME93XgAX1`. Telnyx 10DLC: brand `B0YDBOE` verified, campaign `CNZVYD6` Active, messaging profile created, DID `+16296001047` Active. Path A demo executed end-to-end: admin manual order → Telnyx SMS → Stripe Checkout (test mode) → `checkout.session.completed` webhook → order flipped to paid → Supabase Realtime push → kitchen tablet shows order in **New** column → tap-advance through statuses works. Two ship-blockers found and fixed in flight: (1) `_shared/http.ts` was missing CORS preflight handling for browser-callable Edge Functions, breaking `admin_create_manual_order` from the admin SPA — added `handlePreflight()` helper; (2) auth init in both apps relied solely on `onAuthStateChange` firing `INITIAL_SESSION`, leaving the spinner stuck forever on refresh in production conditions — bootstrap from `getSession()` first, then subscribe (PR #1). Two known follow-ups documented: Stripe success_url 404 on the marketing site, and Path B (Vapi response shape rewrite for live voice integration). - SMS provider swapped Twilio → Telnyx (May 1, 2026). New `_shared/telnyx.ts` (Bearer auth, JSON `POST /v2/messages`, `messaging_profile_id` preferred over `from`); `_shared/twilio.ts` removed. Env vars: `TELNYX_API_KEY`, `TELNYX_MESSAGING_PROFILE_ID`, `TELNYX_FROM_NUMBER`, `TELNYX_MOCK_MODE`. Mock mode preserved (id prefix `TX_mock_`). Sui's Sushi seeded with the Telnyx DID `+16296001047`. `sms_messages.twilio_sid` / `twilio_status` columns kept under their historical names — provider-agnostic in meaning, no rename migration. `pnpm ci:fast` green. - M8 End-to-end demo (code half) delivered: `wrangler.toml` for admin + kitchen, `deploy.yml` workflow, `checkout.session.expired → failed` integration test, post-MVP backlog in `github/ISSUES/008-m8-end-to-end-demo.md`. `pnpm ci:fast` green (Apr 19, 2026). - M7 Stripe + SMS wiring delivered (Apr 19, 2026). Shared payment module (`packages/shared/src/payment/`) with Checkout Session builder + Stripe form-body encoder, Web Crypto–backed webhook signature verifier, and SMS body templates. Edge Function shared helpers (`_shared/stripe.ts`, `_shared/telnyx.ts` [originally `_shared/twilio.ts`, swapped May 1], `_shared/sms.ts`) with deterministic mock-mode when `STRIPE_SECRET_KEY` / `TELNYX_API_KEY` are unset. New Edge Functions: `stripe_webhook` (signature verification, idempotency via UNIQUE on `payment_events.stripe_event_id`, `checkout.session.completed` → `paid`, `payment_intent.payment_failed` → `failed`) and `admin_create_manual_order` (JWT-auth-gated; cash path calls `finalize_cash_order()` RPC at `status='paid'` + `payment_events(kind='cash_at_counter')`, card path calls `create_pending_card_order()` RPC then Stripe + SMS). `vapi_finalize_order` now creates a real Checkout Session and persists `stripe_payment_url`; `vapi_call_start` is idempotent on `vapi_call_id` (retries don't wipe carts); `vapi_call_end` dispatches the payment-link SMS on `order_placed`. Migration `0006_m7_payments.sql`: new `payment_method` + `payment_event_kind` enums, `orders.payment_method` / `orders.created_by_staff_id` / `orders.stripe_payment_url`, `payment_events.kind` + shape-check. Admin New Order page gained a payment-method radio; Admin Settings > Payments page stubs Stripe Connect onboarding (full UI in M11). 188 unit tests + 68 integration tests green. From 6dbd0640fcd0c319357797c573c44fdcb56beadb Mon Sep 17 00:00:00 2001 From: scotton Date: Sat, 2 May 2026 13:21:55 -0500 Subject: [PATCH 2/3] =?UTF-8?q?docs(journal):=20May=202=20entry=20?= =?UTF-8?q?=E2=80=94=20M8=20Path=20A=20demo=20proven=20live?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the dated developer-journal entry covering: - Cloud stack provisioned + Path A demo executed end-to-end - Two ship-blockers fixed in flight (CORS preflight, auth init bootstrap) - The three-Stripe-environment confusion and Workbench Shell resolution - Path A vs Path B split decision (Vapi voice deferred) - Open follow-ups: success_url 404 (sibling dialtone_menu repo), Path B rewrite, orphaned pending_payment test orders, Telnyx provisioning resolved on its own Same theme as the lessons-learned + project-status updates on this PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- developer/developer-journal.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/developer/developer-journal.md b/developer/developer-journal.md index 4873d3b..b4b6313 100644 --- a/developer/developer-journal.md +++ b/developer/developer-journal.md @@ -4,6 +4,31 @@ Chronological record of implementation decisions, changes made, and why. Most re --- +## May 2, 2026 — M8 Path A demo proven live; two ship-blockers fixed in flight; one customer-facing 404 deferred + +**What was done:** + +- Closed the M8 demo end-to-end on the live cloud stack: cloud Supabase project `klzznfagrtormretqsgb` provisioned, migrations + seed applied, both apps deployed at `admin.dialtone.menu` + `kitchen.dialtone.menu` with TLS, Stripe Connect platform active with Sui's Sushi as connected account `acct_1TSRbH2ME93XgAX1`, Telnyx 10DLC fully wired (brand `B0YDBOE` verified, campaign `CNZVYD6` Active, messaging profile created, DID `+16296001047` Active), Stripe webhook registered against the cloud Edge Function URL. Path A demo path executed live: admin manual card order → Telnyx SMS → Stripe Checkout (test mode) → `checkout.session.completed` webhook → order flipped to `paid` → Supabase Realtime push → kitchen tablet shows order in **New** column → tap-advance through Preparing → Ready → Completed all worked. +- Fixed two ship-blockers that surfaced only under live conditions and weren't caught by integration tests: + - **CORS preflight** — `_shared/http.ts` was authored in M6 for Vapi-only server-to-server traffic and explicitly omitted CORS handling. M7 added `admin_create_manual_order` which is called from the admin SPA in a browser; the missing OPTIONS preflight response blocked the actual POST silently (no Edge Function invocation, no useful error in the UI). Added a `corsHeaders` constant + `handlePreflight()` helper in `_shared/http.ts`; `admin_create_manual_order` now calls `handlePreflight(req)` before any other logic. Future browser-callable Edge Functions need the same call. Shipped on `main` directly because the admin app was unusable without it. + - **Auth init bootstrap (PR #1)** — both `apps/admin/src/lib/auth-context.tsx` and `apps/kitchen/src/app.tsx` relied solely on `supabase.auth.onAuthStateChange` firing `INITIAL_SESSION` on mount to flip the `loading` flag to false. In some browser/SDK combinations a restored session never produces that event, leaving the spinner stuck forever after a refresh. Bootstrap now calls `supabase.auth.getSession()` synchronously on mount and runs the same handler; the listener early-returns on `INITIAL_SESSION` to avoid duplicate DB queries. Greptile flagged a P1 (loading flash) and P2 (duplicate queries) on the initial PR — both addressed in the same branch before merge. +- Documented the live-deploy lessons learned in `developer/m8-live-demo-checklist.md` and refreshed `docs/project-status.md` to mark M8 Path A as proven and call out the two M8 fast-follows. Cross-noted the customer-facing redirect work in the sibling `dialtone_menu/` repo's `AGENTS.md` so the team there can pick it up. + +**Decisions and rationale:** + +- **Three parallel Stripe environments are real and silently break things.** Modern Stripe accounts have live mode, legacy `/test/...` test mode, and Workbench Sandboxes — three independent envs each with their own API keys, webhooks, and event streams. We burned over an hour debugging "why doesn't `checkout.session.completed` deliver" — the answer was that the webhook was registered in one env, the API key was from another, so events fired in env A and looked for a webhook in env B. Resolution: register webhooks via **Stripe Workbench Shell** (`stripe webhook_endpoints create ...`) which runs in whatever env you're currently viewing, guaranteeing key + webhook live in the same env. Captured in the M8 checklist's "Lessons learned" section. +- **Demo path bifurcated into A (admin manual order) and B (Vapi voice).** During Vapi assistant setup we discovered that `vapi_call_start` returns its own JSON shape — `{ status, prompt, tools, first_message, context }` — that does not match Vapi's expected `assistant-request` response contract `{ assistant: {...} }`. Additionally, Vapi sends multiple lifecycle event types (`call-start`, `end-of-call-report`, `function-call`, `status-update`) to a single server URL; our function only handles `call-start`-shaped requests. The 60 integration tests in `packages/shared/test/db/voice.test.ts` pass against our own request/response contract, not Vapi's. Rather than rewrite under demo time pressure, deferred the voice path to a focused fast-follow ("Path B") and proved the same end-to-end plumbing via the admin manual order flow ("Path A") which is fully wired through M7 and uses the exact same Stripe + Telnyx + kitchen-Realtime path. +- **Skip `INITIAL_SESSION` in the listener after `getSession()` bootstrap.** Initial reflex was to let both paths run and rely on the handler being idempotent. Greptile correctly flagged that this doubles DB queries on every mount (admin: 6 instead of 3; kitchen: 4 instead of 2) and creates a possible "board → spinner → board" flash if timing aligns badly. Cleaner pattern: bootstrap from `getSession()`, listener handles only subsequent events (`SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`). The original bug we set out to fix still resolves because `getSession()` always runs. + +**Follow-ups / known issues:** + +- **Stripe `success_url` 404 — customer-facing.** `admin_create_manual_order` builds `success_url = ${DIALTONE_PUBLIC_BASE_URL}/orders/${orderId}/paid` (and `cancel_url` similarly). Default `DIALTONE_PUBLIC_BASE_URL` is `https://dialtone.menu` — the marketing site, which has no `/orders/...` route. A paying customer hits the marketing 404 page after Stripe redirects them. **Webhook fires regardless** so the order itself flips to `paid` correctly; only the customer-facing landing is broken. Architectural decision: customer-facing pages belong in the `dialtone_menu/` repo (proper branding, lightweight bundle, customer-facing domain) — not in the admin app. Two static routes need to be added there: `/orders/:id/paid` (success) and `/orders/:id/cancel` (cancel). Both render static thank-you / cancellation copy; they do **not** hit any database (no cross-repo Supabase access, opaque UUID is just a confirmation token). Cross-noted in `dialtone_menu/AGENTS.md`. **Must ship before any real paying customer.** +- **Path B — Vapi voice integration.** Rewrite `vapi_call_start` to return Vapi's `assistant-request` response shape `{ assistant: { model: { messages, tools }, firstMessage } }`; add `message.type` dispatch so the same function handles `call-start`, `end-of-call-report`, etc. Update integration tests to assert on Vapi's actual webhook contract instead of our internal one. Estimated 4–6 hours of focused work. Until done, the voice path is not live — only the admin manual order path proves the demo end-to-end. +- **The two stuck `pending_payment` orders** from today's debugging (`941926c4-...` and `c3b3d220-...`) live in legacy test mode environments where no webhook is registered to handle their eventual `checkout.session.expired` events. They'll stay `pending_payment` indefinitely. Manual cleanup or wait for them to age out; either way orphan test data, not a correctness concern. +- **Telnyx number provisioning resolved on its own.** The DID `+16296001047` flipped from "Pending" to "Active" within ~2h of being attached to the campaign. SMS delivery worked even during the Pending window — Telnyx allows sends to verified numbers (e.g. the account holder's own phone) before full provisioning completes. + +--- + ## April 19, 2026 — PR #18 review fixes for M7 Stripe + SMS wiring **What was done:** From 3a250f6a911881afa1b96f943984d1810e836e7e Mon Sep 17 00:00:00 2001 From: scotton Date: Sat, 2 May 2026 14:19:25 -0500 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20address=20Greptile=20P2=20=E2=80=94?= =?UTF-8?q?=20replace=20concrete=20production=20IDs=20with=20placeholders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile flagged on PR #2 that real production identifiers — Stripe Connect account `acct_1TSRbH2ME93XgAX1` and Telnyx TCR brand/campaign IDs `B0YDBOE` / `CNZVYD6` — were being committed to docs and would become permanently discoverable in git history. Replaced with angle-bracket placeholders across: - developer/m8-live-demo-checklist.md - docs/project-status.md - developer/developer-journal.md Kept as-is (not actually sensitive in our context): - Supabase project ref `klzznfagrtormretqsgb` — already baked into the deployed admin/kitchen JS bundles as VITE_SUPABASE_URL; obfuscating in docs while leaking in the bundle is theater - Telnyx DID `+16296001047` — the customer-facing public phone number (will be on Sui's Sushi's published menu and marketing) The original concrete values are now only in this PR's earlier commits' git history; future references in docs use the placeholder convention. Actual values live in Stripe dashboard / Telnyx Mission Control / 1Password. Co-Authored-By: Claude Opus 4.7 (1M context) --- developer/developer-journal.md | 2 +- developer/m8-live-demo-checklist.md | 4 ++-- docs/project-status.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/developer/developer-journal.md b/developer/developer-journal.md index b4b6313..ffd80fc 100644 --- a/developer/developer-journal.md +++ b/developer/developer-journal.md @@ -8,7 +8,7 @@ Chronological record of implementation decisions, changes made, and why. Most re **What was done:** -- Closed the M8 demo end-to-end on the live cloud stack: cloud Supabase project `klzznfagrtormretqsgb` provisioned, migrations + seed applied, both apps deployed at `admin.dialtone.menu` + `kitchen.dialtone.menu` with TLS, Stripe Connect platform active with Sui's Sushi as connected account `acct_1TSRbH2ME93XgAX1`, Telnyx 10DLC fully wired (brand `B0YDBOE` verified, campaign `CNZVYD6` Active, messaging profile created, DID `+16296001047` Active), Stripe webhook registered against the cloud Edge Function URL. Path A demo path executed live: admin manual card order → Telnyx SMS → Stripe Checkout (test mode) → `checkout.session.completed` webhook → order flipped to `paid` → Supabase Realtime push → kitchen tablet shows order in **New** column → tap-advance through Preparing → Ready → Completed all worked. +- Closed the M8 demo end-to-end on the live cloud stack: cloud Supabase project `klzznfagrtormretqsgb` provisioned, migrations + seed applied, both apps deployed at `admin.dialtone.menu` + `kitchen.dialtone.menu` with TLS, Stripe Connect platform active with Sui's Sushi as connected account ``, Telnyx 10DLC fully wired (brand `` verified, campaign `` Active, messaging profile created, DID `+16296001047` Active), Stripe webhook registered against the cloud Edge Function URL. Path A demo path executed live: admin manual card order → Telnyx SMS → Stripe Checkout (test mode) → `checkout.session.completed` webhook → order flipped to `paid` → Supabase Realtime push → kitchen tablet shows order in **New** column → tap-advance through Preparing → Ready → Completed all worked. - Fixed two ship-blockers that surfaced only under live conditions and weren't caught by integration tests: - **CORS preflight** — `_shared/http.ts` was authored in M6 for Vapi-only server-to-server traffic and explicitly omitted CORS handling. M7 added `admin_create_manual_order` which is called from the admin SPA in a browser; the missing OPTIONS preflight response blocked the actual POST silently (no Edge Function invocation, no useful error in the UI). Added a `corsHeaders` constant + `handlePreflight()` helper in `_shared/http.ts`; `admin_create_manual_order` now calls `handlePreflight(req)` before any other logic. Future browser-callable Edge Functions need the same call. Shipped on `main` directly because the admin app was unusable without it. - **Auth init bootstrap (PR #1)** — both `apps/admin/src/lib/auth-context.tsx` and `apps/kitchen/src/app.tsx` relied solely on `supabase.auth.onAuthStateChange` firing `INITIAL_SESSION` on mount to flip the `loading` flag to false. In some browser/SDK combinations a restored session never produces that event, leaving the spinner stuck forever after a refresh. Bootstrap now calls `supabase.auth.getSession()` synchronously on mount and runs the same handler; the listener early-returns on `INITIAL_SESSION` to avoid duplicate DB queries. Greptile flagged a P1 (loading flash) and P2 (duplicate queries) on the initial PR — both addressed in the same branch before merge. diff --git a/developer/m8-live-demo-checklist.md b/developer/m8-live-demo-checklist.md index e60117b..36d5f41 100644 --- a/developer/m8-live-demo-checklist.md +++ b/developer/m8-live-demo-checklist.md @@ -263,8 +263,8 @@ All items below must be checked before M8 is marked fully complete: - [x] `deploy.yml` workflow ran green; admin + kitchen + Edge Functions all deployed - [x] `admin.dialtone.menu` and `kitchen.dialtone.menu` load over HTTPS with valid TLS - [x] Stripe webhook registered; `STRIPE_WEBHOOK_SECRET` set and functions re-deployed -- [x] Stripe Connect platform account active; Sui's Sushi connected account wired (`acct_1TSRbH2ME93XgAX1`) -- [x] Telnyx DID `+16296001047` provisioned and Active; 10DLC brand verified (`B0YDBOE`); campaign Active (`CNZVYD6`); messaging profile created and attached +- [x] Stripe Connect platform account active; Sui's Sushi connected account wired (``) +- [x] Telnyx DID `+16296001047` provisioned and Active; 10DLC brand verified (``); campaign Active (``); messaging profile created and attached - [ ] Vapi assistant created; all tool webhooks pointing to cloud Edge Function URLs; phone number assigned (deferred — see Path B note below) - [x] **Path A demo** executed end-to-end: admin order → Telnyx SMS → Stripe Checkout → webhook → kitchen Realtime board update → tap-advance flow - [ ] Full **voice** demo path (Vapi inbound call → order → SMS → kitchen) — deferred to Path B fast-follow diff --git a/docs/project-status.md b/docs/project-status.md index fef2203..dbb62e7 100644 --- a/docs/project-status.md +++ b/docs/project-status.md @@ -39,7 +39,7 @@ After fast-follows, M9 is next: Reservations — voice reservation booking (`che ## Recently Completed -- **M8 Path A demo proven live** (May 2, 2026). Cloud Supabase project `klzznfagrtormretqsgb` provisioned, migrations + seed applied. Both apps deployed at `admin.dialtone.menu` and `kitchen.dialtone.menu` with TLS. Stripe Connect platform active; Sui's Sushi connected account `acct_1TSRbH2ME93XgAX1`. Telnyx 10DLC: brand `B0YDBOE` verified, campaign `CNZVYD6` Active, messaging profile created, DID `+16296001047` Active. Path A demo executed end-to-end: admin manual order → Telnyx SMS → Stripe Checkout (test mode) → `checkout.session.completed` webhook → order flipped to paid → Supabase Realtime push → kitchen tablet shows order in **New** column → tap-advance through statuses works. Two ship-blockers found and fixed in flight: (1) `_shared/http.ts` was missing CORS preflight handling for browser-callable Edge Functions, breaking `admin_create_manual_order` from the admin SPA — added `handlePreflight()` helper; (2) auth init in both apps relied solely on `onAuthStateChange` firing `INITIAL_SESSION`, leaving the spinner stuck forever on refresh in production conditions — bootstrap from `getSession()` first, then subscribe (PR #1). Two known follow-ups documented: Stripe success_url 404 on the marketing site, and Path B (Vapi response shape rewrite for live voice integration). +- **M8 Path A demo proven live** (May 2, 2026). Cloud Supabase project `klzznfagrtormretqsgb` provisioned, migrations + seed applied. Both apps deployed at `admin.dialtone.menu` and `kitchen.dialtone.menu` with TLS. Stripe Connect platform active; Sui's Sushi connected account ``. Telnyx 10DLC: brand `` verified, campaign `` Active, messaging profile created, DID `+16296001047` Active. Path A demo executed end-to-end: admin manual order → Telnyx SMS → Stripe Checkout (test mode) → `checkout.session.completed` webhook → order flipped to paid → Supabase Realtime push → kitchen tablet shows order in **New** column → tap-advance through statuses works. Two ship-blockers found and fixed in flight: (1) `_shared/http.ts` was missing CORS preflight handling for browser-callable Edge Functions, breaking `admin_create_manual_order` from the admin SPA — added `handlePreflight()` helper; (2) auth init in both apps relied solely on `onAuthStateChange` firing `INITIAL_SESSION`, leaving the spinner stuck forever on refresh in production conditions — bootstrap from `getSession()` first, then subscribe (PR #1). Two known follow-ups documented: Stripe success_url 404 on the marketing site, and Path B (Vapi response shape rewrite for live voice integration). - SMS provider swapped Twilio → Telnyx (May 1, 2026). New `_shared/telnyx.ts` (Bearer auth, JSON `POST /v2/messages`, `messaging_profile_id` preferred over `from`); `_shared/twilio.ts` removed. Env vars: `TELNYX_API_KEY`, `TELNYX_MESSAGING_PROFILE_ID`, `TELNYX_FROM_NUMBER`, `TELNYX_MOCK_MODE`. Mock mode preserved (id prefix `TX_mock_`). Sui's Sushi seeded with the Telnyx DID `+16296001047`. `sms_messages.twilio_sid` / `twilio_status` columns kept under their historical names — provider-agnostic in meaning, no rename migration. `pnpm ci:fast` green. - M8 End-to-end demo (code half) delivered: `wrangler.toml` for admin + kitchen, `deploy.yml` workflow, `checkout.session.expired → failed` integration test, post-MVP backlog in `github/ISSUES/008-m8-end-to-end-demo.md`. `pnpm ci:fast` green (Apr 19, 2026). - M7 Stripe + SMS wiring delivered (Apr 19, 2026). Shared payment module (`packages/shared/src/payment/`) with Checkout Session builder + Stripe form-body encoder, Web Crypto–backed webhook signature verifier, and SMS body templates. Edge Function shared helpers (`_shared/stripe.ts`, `_shared/telnyx.ts` [originally `_shared/twilio.ts`, swapped May 1], `_shared/sms.ts`) with deterministic mock-mode when `STRIPE_SECRET_KEY` / `TELNYX_API_KEY` are unset. New Edge Functions: `stripe_webhook` (signature verification, idempotency via UNIQUE on `payment_events.stripe_event_id`, `checkout.session.completed` → `paid`, `payment_intent.payment_failed` → `failed`) and `admin_create_manual_order` (JWT-auth-gated; cash path calls `finalize_cash_order()` RPC at `status='paid'` + `payment_events(kind='cash_at_counter')`, card path calls `create_pending_card_order()` RPC then Stripe + SMS). `vapi_finalize_order` now creates a real Checkout Session and persists `stripe_payment_url`; `vapi_call_start` is idempotent on `vapi_call_id` (retries don't wipe carts); `vapi_call_end` dispatches the payment-link SMS on `order_placed`. Migration `0006_m7_payments.sql`: new `payment_method` + `payment_event_kind` enums, `orders.payment_method` / `orders.created_by_staff_id` / `orders.stripe_payment_url`, `payment_events.kind` + shape-check. Admin New Order page gained a payment-method radio; Admin Settings > Payments page stubs Stripe Connect onboarding (full UI in M11). 188 unit tests + 68 integration tests green.