fix(stripe): idempotent customer lookup for wallet-auth + stuck pending_payment cleanup#50
Open
aimerdoux wants to merge 19 commits into
Open
fix(stripe): idempotent customer lookup for wallet-auth + stuck pending_payment cleanup#50aimerdoux wants to merge 19 commits into
aimerdoux wants to merge 19 commits into
Conversation
- GET/POST /api/unsubscribe?email=... or ?token={base64url(email)}
- Calls wavex_os_unsubscribe_contact RPC to mark leads.unsubscribed=true
- Migration adds unsubscribed/unsubscribed_at to public.leads + public.genesis_leads
- HTML confirmation page for email click-throughs; JSON for POST/programmatic
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Add Booking.tsx: experience catalog + Stripe checkout redirect flow (/book) - Add booking-intent.ts: POST /api/booking/intent server route - Add "Book Experiences" CTA button to MissionControl header nav - Add signInLabel prop to SignInWidget; use "Sign in to book" on /book page Root cause: /book route existed in router but was unreachable — no page linked to it. 15k page_views on / with 0 path to booking = 0 booking_intents. Co-Authored-By: Paperclip <noreply@paperclip.ing>
…e-issue guard - run-governor.ts: live Anthropic quota windows -> allowance tiers (open/conserve/critical_only/frozen); DEFER-UNTIL cadence gate; 3-strike same-error circuit breaker with auto-pause at 6 - claimQueuedRun consults governor (defer keeps runs queued, never cancels work) - GET /api/governor/status for dashboard quota bar - issue create: 409 duplicate_open_issue on normalized-title match (allowDuplicate: true to bypass); audited 15 dup clusters in one afternoon - docs: optimization plan for the 5 fleet-audit failure modes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ota card QA findings (e2e, 2026-06-10): Composio key VALID (1000 toolkits, mode live); github OAuth initiate returns real redirect URL end-to-end. The 'Composio is disabled' banner was a mislabel — toolkits without Composio-managed auth (e.g. amplitude) threw 'No Default auth config found' and every failure was shown as 'disabled'. initOAuth now classifies: disabled / requires_custom_credentials / authorize_failed; worker + UI surface the right message. Dashboard Month Spend card now shows live provider quota % and governor tier when available. KNOWN FOLLOW-UP: @composio/core 0.9.1 uses deprecated connectedAccounts. initiate() — sunset 2026-07-03; upgrade to 0.10.0 before then. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…strip The 441-line MissionControlDeliverablesWidget (and its registered mission-control-deliverables worker RPC) existed but was never rendered — the deliverables inbox was built and unmounted. Adds it as a first-class tab after Decisions. Adds an always-visible GovernorQuotaStrip reading GET /api/governor/status (same signal the run scheduler obeys): progress bar, tier color, window label, reset time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stale-run reaper only honored a live processPid for SESSIONED_LOCAL_ADAPTERS; plateau_local runs (external adapter, pid reported via onSpawn) were reaped as process_lost at exactly staleThreshold (~5m) while the child was alive — reproduced 3x on CEO canary runs. Orphaned children kept running unbilled^W untracked. Adding plateau_local lets the reaper mark them detached-but-alive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Toolkits without Composio-managed OAuth (e.g. Amplitude) dead-ended with
'requires_custom_credentials'. Full path added:
- shim: getCredentialRequirements (merges auth-config creation + connected-
account initiation fields; picks API_KEY > BEARER_TOKEN > BASIC > OAUTH2)
and connectWithCredentials (use_custom_auth config + initiate with
{authScheme, val} for key-style schemes)
- server: GET credential-requirements/:slug + POST connect-with-credentials
- plugin worker: two actions proxying the routes
- Directory UI: inline credential form opens on requires_custom_credentials
(password-masked secret fields, required validation)
Live-verified: GET credential-requirements/amplitude serves real fields
(Base URL, API Key…). Write path follows the SDK contract validated by the
github authorize e2e; first real credential submit confirms it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… pre-OAuth fields
googleads/whatsapp failed authorize() with ConnectedAccount_MissingRequiredFields
(Customer ID / WABA ID). New classification routes them to the same credential
form; connectWithCredentials uses the Composio-MANAGED auth config for
OAuth-style schemes and carries the user's fields on initiate ({authScheme,val}),
returning the OAuth redirect. fieldsFor=initiation scopes the form to only the
fields the managed app actually needs (no client id/secret over-ask).
Live-verified: googleads initiate -> requires_initiation_fields; initiation
fields = [Customer ID]; whatsapp -> API_KEY [System User Token, WABA ID].
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wake a fixer agent run_failed + run_completed emitted at setRunStatus; breaker_paused from the governor. Hot-read JSON config, per-signature dedup (30m), hourly wake cap, fixer self-trigger guard, fire-and-forget. Fixer wakes ride the normal queue so the run governor still applies. Pool B doc: docs/inference-hook-manager.md. Also fixes TS closure-narrowing in run-governor scan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ission Control KPIs
bridgeKpis now lands manifest.goal {kpiId, current, target, days} as a
first-class scoreboard KPI: canonical KPIs get the user's target/window
stamped; non-canonical goals get their own company_kpis row labeled with
meta_goal; baseline kpi_snapshot uses the user's stated CURRENT value
(micros) instead of zero. Mission Control's observer surface now starts
with the mission the user actually typed during onboarding.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Writer/metrics targeted /rest/v1/wavex_os.wizard_events — PostgREST doesn't take schema.table in the path, so every call 404'd and onboarding funnel telemetry was never recorded (QA finding #1, 2026-06-10). Table created in the wavex experiences Supabase project (migration create_wizard_events, RLS on). E2E verified: POST persists (event dcc57c7f), metrics returns funnel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ingestion Concurrency is the real burn-rate control: a freshly incepted fleet ran 13 high-priority runs concurrently and burned the 5h window 50%->68% in ~25min (priority gating passed them all). Caps: open 6 / conserve 3 / critical 1 / frozen 0 (env-tunable). POST /api/hooks/emit lets sibling processes (onboarding server :3101, edge fns) land errors on the inference surface; new onboarding_error hook type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…meric suffixes The collision handler appended an 'A' per attempt, yielding WAVAAAAAAAA by the ninth wavex-os/* company. Now derives from the last name segment (wavex-os/wavexcard -> WAV, WAVE, WAVEX, WAVEXC, WVXC consonant skeleton), then numeric suffixes (WAV2..). Existing companies keep their prefixes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, goal target, stated baseline The scoreboard read only expected_kpi_impacts: a fresh company rendered EMPTY and the user's onboarding goal (MRR 5000 -> 15000/90d) never reached the observer surface. Now: seeded company_kpis rows appear immediately; target falls back to company_kpis.targetMicros when no impacts exist; current prefers the user's stated baseline (kpi_snapshots) over the sampler's no-recorder zero until a real non-zero measurement lands. Verified live for wavexcard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion, ignition charter, honest Monte Carlo - Roadmap/workflows map: new mission-control-roadmap RPC (Paperclip-direct) + MissionControlRoadmapWidget + tab; renders ignition's [Roadmap] workflows with owner, status, KPI tags, child progress (live-verified: 5 lanes). - Inference surface over onboarding: /api/hooks/emit resolves wavex slugs; wavex-os-server emits onboarding_error (manifest cloud-sync) and connector_failed (composio authorize) via inference-hooks-client (e2e verified into company activity). - Silent ignition programmer: ignition now seeds the MIDDLEWARE supervisor charter assigned to the Chief of Staff (step middleware_charter) — previously installed by hand after every onboarding. - Monte Carlo honesty: the simulation was real (activation growth 24-46% differentiated strategies) but pre_scale mode freezes MRR by design and every surface narrated the frozen MRR stats as zeros. Winner now carries mean_activation_growth + model_mode; imprint leads with activation lift + burn (verified: RETENTION_FIRST 45.7% / burn 1.31); MonteCarloRace chart races on activation when MRR is degenerate instead of flatlining. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…AA-154) Adds infrastructure for the two-part referral messaging sequence: - Migration: wavex_os.referral_sends audit table, blast/nurture candidate query RPCs with lazy referral code creation, idempotent record_send RPC - referral-blast.ts: one-shot startup job sending launch blast to all active members reachable via Telegram; dry-run by default (WAVEX_REFERRAL_BLAST_DRY_RUN) - referral-nurture.ts: hourly scheduler sending Day-14 nurture to members with 0 converts once 14 days post-blast; dry-run by default (WAVEX_REFERRAL_NURTURE_DRY_RUN) - index.ts: wires both jobs into server startup Co-Authored-By: Paperclip <noreply@paperclip.ing>
… pending_payment cleanup (WAVAAAAAAAA-164) - create-booking-checkout-session: resolve Stripe customer by supabase_user_id metadata first, then real_email from user_metadata, to prevent duplicate customer creation for wallet-auth users (@wallet.wavex.app proxy emails) - create-checkout-session: same fix for subscription checkout path - booking-intent-cleanup: also call wavex_os_cancel_expired_pending_payment_intents every 5 min to unstick intents whose checkout.session.expired webhook was missed - migration 20260610000008: (1) BEFORE trigger enforces cancelled_at = now() on any status → 'cancelled' transition regardless of calling path; (2) update wavex_os_set_booking_intent_pending_payment to accept pending_payment → pending_payment re-entry so expired-session retries work; (3) new RPC wavex_os_cancel_expired_pending_payment_intents cancels pending_payment intents older than 25 h (Stripe session TTL = 24 h) Co-Authored-By: Paperclip <noreply@paperclip.ing>
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
Fixes three issues from WAVAAAAAAAA-164 affecting Omar Hernandez (wallet-auth user with 6 stuck
pending_paymentbooking intents).stripe.customers.list({ email })used the wallet proxy email (@wallet.wavex.app) — never matched an existing customer, creating a new one each sessionsupabase_user_idmetadata first, then fall back toreal_emailfromuser_metadata, then createwavex_os_cancel_expired_pending_payment_intentscancelspending_paymentintents older than 25 h (Stripe session TTL = 24 h). Runs alongside the existing stale-pending cleanup every 5 min — will unstick the 6 intents on first deploycancelled_atinvariant: BEFORE trigger onbooking_intentsback-fillscancelled_at = now()on anystatus → 'cancelled'transition, regardless of calling pathwavex_os_set_booking_intent_pending_paymentnow acceptspending_payment → pending_paymentre-entry so callers can refresh the checkout session ID without resetting the intent firstTest plan
supabase_user_idmetadata)pending_paymentintent for >25 h; verify cleanup job cancels it withcancelled_atsetbooking_intenttostatus='cancelled'withoutcancelled_at; confirm trigger back-fills itcreate-booking-checkout-sessionfor an intent already inpending_paymentwith expired session; confirm new URL returned and session ID updated in DB🤖 Generated with Claude Code