docs: capture M8 live-deploy lessons learned + Path A demo proven#2
Merged
ByteStreams-AI merged 3 commits intomainfrom May 2, 2026
Merged
docs: capture M8 live-deploy lessons learned + Path A demo proven#2ByteStreams-AI merged 3 commits intomainfrom
ByteStreams-AI merged 3 commits intomainfrom
Conversation
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
…eholders 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) <noreply@anthropic.com>
Collaborator
Author
|
Addressed Greptile's P2/security finding in Replaced with angle-bracket placeholders across
Kept as-is (not actually sensitive in our context):
Note on git history: the original concrete values are still in this PR's earlier commits' history (per Greptile's note that they're "permanently discoverable"). Going forward, docs reference placeholders only. Actual values live in Stripe dashboard / Telnyx Mission Control / 1Password — not in source. Validated: |
1 task
ByteStreams-AI
pushed a commit
that referenced
this pull request
May 2, 2026
Captures the May 2 work after PRs #2/#3/#4 merged so picking up cold in 2-3 days doesn't require re-discovering anything. README.md - Status bumped from "M1–M7 complete... Next: M8" to "M1–M8 complete, Path A demo proven live" - Stack updated (Telnyx replaces Twilio; Stripe Connect routing now M11 not M7) - "Where things live" table refreshed: planned-migrations now empty, added Edge Function + payment + branding entries, called out that customer-facing post-payment pages live in admin (not the marketing site at dialtone_menu) - Added pointer to developer/m8-live-demo-checklist.md for the deploy runbook + lessons learned AGENTS.md - Current state header rewritten — single paragraph covering today's four PRs and where to look next - Conventions extended with five new bullets agents commonly miss: - Browser-callable Edge Functions need handlePreflight() (M8 lesson) - Auth init must bootstrap from getSession() before subscribing (PR #1) - Per-restaurant branding location + access patterns (RPC vs useAuth-loaded row) (PRs #3 + #4) - Customer-facing /orders/:id/{paid,cancel} live in admin app, not marketing site (PR #4) - Three-Stripe-environment gotcha (M8 lesson) - New "Open follow-ups" section at the bottom with three items — admin chrome branding, kitchen Ready SMS, Path B Vapi — each with scope, estimated effort, and where to start developer/developer-journal.md - New dated entry "May 2 (later)" picking up after the existing May 2 entry (which only covered through Path A demo). Documents PR #3 + PR #4, the dialtone_menu PR #27 reversal, the SQL cleanup of orphaned test orders, and the doc refresh itself - Decisions section: why columns on restaurants over a separate table, why RPC scoped by order_id over slug, why helpers extracted for testability, why hexToRgba falls back to opaque on bad input - Follow-ups section mirrors AGENTS.md "Open follow-ups" — same three items so journal readers + agent readers see the same plan 220 unit tests still pass; lint + typecheck green. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 4, 2026
ByteStreams-AI
added a commit
that referenced
this pull request
May 5, 2026
…nce (#27) * fix(voice): cart integrity + clean termination + SMS dispatch resilience Seven live-call findings rolled into one PR. All M11 ramp catches. 1. Phone readback says "+1". ElevenLabs reads "+19015551234" as "plus one nine zero one..." which sounds robotic. Added formatPhoneForSpeech() helper: "+19015551234" → "(901) 555-1234" (US) "+447911123456" → "+447911123456" (international, unchanged) PromptContext now carries caller_phone_spoken alongside the raw E.164 caller_phone. Step 6 of the order flow reads the spoken form and the prompt explicitly tells the LLM not to add a "plus one" prefix. 2. Call doesn't hang up after agent says goodbye. Our custom end_call tool returned ok server-side but never told Vapi to terminate. Result: agent says "thanks, goodbye" → Vapi sits in idle-message limbo → customer eventually hangs up. Two layer fix: - end_call's tool wrap now carries a request-complete message with endCallAfterSpoken=true. Vapi physically terminates after the LLM's next utterance once our handler returns. - endCallPhrases expanded from 7 to 15 phrases covering the wider goodbye vocabulary the LLM uses naturally ("thanks for calling", "have a wonderful evening", "take care", "enjoy your meal", etc). Substring-matched, so shorter forms catch longer variants. 3. Payment-link SMS never sent. Same root cause as #2: when end_call doesn't fire, ended_reason=='customer-ended-call' and our SMS dispatch was gated on === 'order_placed'. Order sat in pending_payment forever. Loosened the gate: dispatch when order_id exists AND ended_reason !== 'customer_declined'. The decline path still blocks (LLM invokes end_call(reason='customer_declined') in step 10 when caller says no to consent in step 8); everything else sends. Risk of sending without consent is bounded — caller initiated the transactional order, and the brief consent-skipped window is acceptable under 10DLC for caller-initiated transactions. 4. Ghost items — verbal "got it" without add_item_to_order call. tool_log from a live call: 1 add_item entry, but the caller ordered 2 items. The LLM verbally confirmed both but only invoked the tool for one. The customer heard the readback miss the second item and lost confidence in the system. Three layered fixes: - Server-side verification: new vapi_get_cart_state Edge Function returns the cart as the server sees it (truth from voice_calls.raw_payload, not LLM memory). - Prompt step 5: BEFORE finalize_order, call get_cart_state and read the items back to the caller. If they spot something missing, recover via add_item_to_order and re-verify. Only then capture name + finalize. - Step 4e tightening: explicit rule that verbal acknowledgement is NOT a substitute for the tool call — "ITEM IS NOT IN THE CART until add_item_to_order returns successfully". Recovery clause for when the LLM realizes mid-turn it skipped the tool. Tools order in selectTools() now: get_menu, add_item_to_order, remove_last_item, get_cart_state, finalize_order. Tool-list assertion in voice.test.ts updated. Tests: - 4 new prompt-template tests cover the cart-verification gate, verbal-only-ack ban, spoken phone format, and the prompt rule ordering (cart verify → name → phone → finalize). - formatPhoneForSpeech has its own test in numbers.test.ts — US, international, empty, fallback. - Tool-list ordering test asserts get_cart_state immediately precedes finalize_order. - 297/297 unit. No migration. CI Deploy will roll Edge Functions + prompt changes to cloud automatically once merged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(voice): independent cancel signal + JSDoc accuracy Addresses Greptile review on PR #27. P1 — decline-path SMS leak when LLM forgets end_call. The previous SMS dispatch gate was `ended_reason !== 'customer_declined'`, which only fires if the LLM invokes end_call(reason='customer_declined'). But the entire reason this PR loosens dispatch in the first place is that the LLM sometimes skips end_call. So the decline path inherited the same unreliability. Fix: a separate cancel_order tool the LLM calls in step 10 BEFORE end_call. cancel_order flips orders.status to 'cancelled' on the server (independent of any subsequent tool call), and vapi_call_end now has THREE gates on SMS dispatch: Gate 1: order.status === 'cancelled' — server-side state, set by cancel_order. Reliable even if end_call is forgotten. Gate 2: ended_reason === 'customer_declined' — set by end_call. Catches cases where cancel_order was forgotten but end_call was invoked. Loose default: order_id + smsTarget — caller-initiated transactional dispatch. Catches happy-path forgetfulness. Implementation: - New cancel_order tool (tools.ts) with reason: required string. - New vapi_cancel_order Edge Function. Idempotent on already-cancelled. Validates the order is in pending_payment before transitioning. Optimistic-concurrency guard via .eq('status', 'pending_payment'). Cancellation reason stored as a structured prefix on orders.notes (no migration in this PR; full cancellation_reason column is M12 admin order history work). - Wired into vapi_call_start dispatcher. - Prompt step 10 now: FIRST call cancel_order (mandatory, with the rationale spelled out), THEN acknowledge politely, THEN end_call. - vapi_call_end loads order.status before the SMS dispatch decision and skips when status === 'cancelled'. P2 — JSDoc on formatPhoneForSpeech showed a regrouped international example ("+447911123456" → "+44 7911 123456") that didn't match the code's actual return value. The code returns the raw E.164. JSDoc fixed to match — TTS handles spacing/grouping for international. Tests: - 1 new prompt test asserts cancel_order appears BEFORE end_call in the decline branch. - selectTools list test updated; ORDERING_TOOLS too. - voice.test.ts integration tool-list assertion updated. - 299/299 unit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Documents what actually happened during the live M8 deploy on May 2, 2026 so the next setup (e.g. staging environment, second restaurant onboarding) doesn't re-discover the same issues.
Files:
developer/m8-live-demo-checklist.md— completion criteria updated to reflect Path A done; new "Lessons learned" section covers Stripe env confusion, CORS preflight, auth init pattern, success_url 404, Path B deferraldocs/project-status.md— Current Status updated, Recently Completed gains detailed M8 entry, Current Focus shows the two M8 fast-follows before M9Two known follow-ups documented (not fixed in this PR):
success_urllands ondialtone.menu/orders/:id/paidwhich 404s — webhook fires regardless so order flips to paid, but customer-facing experience is brokenvapi_call_startresponse shape doesn't match Vapi'sassistant-requestcontract; voice integration was never live-testedTest plan
pnpm ci:faststill green (188 unit tests)🤖 Generated with Claude Code
Greptile Summary
Docs-only PR capturing M8 live-deployment lessons learned: completion criteria updated to reflect Path A proven end-to-end, and a new "Lessons Learned" section documents the Stripe environment confusion, CORS preflight gap, auth init bootstrap fix,
success_url404, and Path B deferral. No behavior changes; two known follow-ups (success_url 404 and Vapi response shape rewrite) are explicitly tracked as fast-follows before M9.Confidence Score: 5/5
Docs-only PR with no behavior changes; safe to merge.
Only P2 finding present: the M8 Milestone Summary table row in project-status.md was not updated to match the live-deploy narrative in the rest of the document. No P0 or P1 issues identified.
docs/project-status.md — M8 roadmap table row note is stale relative to the updated Current Status and Recently Completed sections.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD subgraph PathA["Path A — Proven Live (May 2, 2026)"] A1[Admin SPA] -->|POST admin_create_manual_order| A2[Edge Function\nhandlePreflight → CORS fix] A2 -->|card path| A3[Stripe Checkout Session] A3 -->|customer pays| A4[checkout.session.completed\nwebhook] A4 -->|order → paid| A5[Supabase Realtime push] A5 --> A6[Kitchen tablet\nNew → Preparing → Ready → Completed] A2 -->|Telnyx SMS| A7[Customer receives\npayment link] end subgraph PathB["Path B — Deferred (voice contract mismatch)"] B1[Vapi inbound call] -->|assistant-request| B2[vapi_call_start\n⚠ returns wrong shape] B2 -.->|needs rewrite| B3["Expected: { assistant: {...} }\nActual: { status, prompt, tools, ... }"] B3 -.->|4–6h focused work| B4[Path B unblocked] end subgraph KnownIssues["Known Open Issues"] I1["success_url → dialtone.menu/orders/:id/paid\n(404 on marketing site)\nWebhook fires ✓ — UX broken ✗"] I2["Failure modes not yet validated\n(abandoned / declined / replay)"] endReviews (4): Last reviewed commit: "docs: address Greptile P2 — replace conc..." | Re-trigger Greptile