Six parallel audits · 15 August 2026
What happens to Insights if a thousand people arrive next week behind serious ad spend. Six agents swept the app in parallel — onboarding, deck, recommender, monetization, scale and analytics. Everything below was verified against live code and the production database.
I reported that conclusion confidently and put it in the wiki as a redesign input. It was an artifact of comparing two different time periods.
this_is_me and pass are emitted only by the swipe deck, and their first rows ever are dated 10 August 2026 — the day the deck shipped. The story_view (2,477) and opened (652) counts I set them against reach back to April: four months of the old scroll feed, a surface with no keep/pass concept at all.
Scoped to the deck's actual five-day lifetime, six distinct humans have used it — and among them the swipe is the dominant action:
| Action since 10 Aug | Events | People |
|---|---|---|
| pass (swipe left) | 29 | 4 |
| save_for_later (swipe right) | 29 | 4 |
| this_is_me (swipe right) | 13 | 3 |
| opened (tap to read) | 22 | 6 |
71 swipe actions against 22 opens — and the opens are themselves roughly 40% inflated by a double-write. The swipe is being used. There is no reader-versus-swiper problem to design around; there is a five-day-old surface with almost no traffic on it yet.
This is not a design problem. It is two bugs, both live today, both already fixed on the work branch.
Lockout one — bounce off the onboarding paywall and you can never get back in. The onboarding paywall is presented with showNoThansks: false and no dismiss (OnboardingFlowView.swift:1435). The escape hatch that records you reached it — onboardingPaywallReached — has zero occurrences on paths-v1 and six on the work branch. So a user who quits at that screen replays all nine onboarding screens on next launch and meets the identical wall. They can never reach the free experience. The only exit is subscribing.
Lockout two — deep-link arrivals loop forever. On live, completing the onboarding sheet from a deep link never sets hasSeenOnboarding. Tap a card → onboard → hit the hard paywall → complete it → tap another card → onboard again. Infinite, and it poisons the entire share and SEO acquisition funnel.
239 users, 130 of whom ever generated a single interaction. That 109-person gap is the wall. And it is real behaviour, not an instrumentation artifact — I computed it from card_interactions rows in the database, not from Amplitude.
Driving a thousand paid installs into this build means paying for a lockout.
In the order they'd cost you money.
Three independent failures on paths-v1, all fixed on the work branch:
FacebookClientToken — the SDK initialises but can't authenticate, so its event stream doesn't land.requestTrackingAuthorization doesn't exist on live. Users are never asked, so the IDFA is never available.isAdvertiserTrackingEnabled is never set, so FBSDK won't use the IDFA even if granted.SKAdNetwork still works (7 call sites, both SKAN ids are Meta's) so you'd get coarse delayed postbacks — and nothing else.
Compounding it: Meta only ever receives .startTrial and .subscribe. There is no signup event. Meta needs ~50 conversions per ad set per week to leave the learning phase; you have 11 payers in the product's lifetime. A purchase-optimised campaign cannot converge. Wire CompleteRegistration on signup in the same build.
500 unclaimed codes, created 9 August, all exactly 18 characters — Apple's subscription Offer Code format. IAP promo codes are 12 characters and capped at 100 per version, so 500 of them cannot be cards10 codes.
RootView.swift:62-69 presents ReferFriendSheet() on nothing but pendingReferral && state == .content — no entitlement check, and it's live. Anyone who opens insights://refer or the /refer page can draw a code. The app then grants no cards, because only cards10 is credited — so the user is also told something untrue.
Check App Store Connect before shipping, and gate or disable that entry.
Six of the thirteen identify traits exist only on the work branch: notification_consent, plan_name, is_subscriber, subscription_expires_at, ideas_read, my_insights_ready.
Five of the nine planned CRM segments are therefore unbuildable — Trialist, Paid/Power, Cancelled and both dormancy tiers all need plan state that never arrives. Every trial→paid and win-back campaign has no attribute to run on.
Push reachability is 15.9% (38 of 239). 77 users granted OS permission in 90 days; only 38 tokens exist. The live build registers for APNs exactly once, inside onboarding, and the consent write races the async callback so the row is often stored with a null token.
The prompt asks Claude for exactly 3 cards (recommendation_agent.py:320, and the JSON schema at :328 has three slots). The gate immediately after demands ten:
DAILY_CARD_COUNT = 10 | line 33 |
picks = [...][:DAILY_CARD_COUNT] | line 339 |
if len(picks) < DAILY_CARD_COUNT: return None | line 340 — always true |
The Haiku call runs and is billed at line 330, then the result is discarded at line 340. Every user, every night.
Verified in the data: of 191 prepared_cards rows, zero were written in the 03:00 window and zero carry journey_stage='live' — the stage only the agent emits. Every row comes from the request path or onboarding. Origin: commit b9cd3e5 on 4 August bumped the constant 3→10 and never touched the prompt.
At 1,000 users that is roughly $99/month of Anthropic spend producing nothing. The fix is one line.
Measured against live infrastructure: Supabase Micro — 2 burstable cores, max_connections 60 — and a single uvicorn worker.
routers/cards.py:905 schedules recompute_and_cache on every signal, ungated by type. story_view fires per slide and is 57% of all traffic. Each recompute is 19–26 serial round-trips plus 2–3 match_cards calls at a measured 222 ms brute-force seq scan each — about 0.55 s of database CPU per swipe. Two cores gives a ceiling near 45 concurrently-reading users. One condition at line 900 removes ~90% of it.
/v1/paths truncates at 781 signups — mid-week. routers/paths.py:96 reads user_path_progress unpaged; PostgREST caps at 1,000 rows and returns 200. At 1.285 rows per signup you cross it partway through the campaign. It feeds the onboarding journey picker's ranking, then freezes into a 30-minute cache. fetch_all() already exists.
stories, quests and key_insights have no index on card_id, so every /v2/daily filters ~50,000 rows and touches ~24 MB. Three indexes take 55 ms to about 2 ms.
recommendation_agent.py:369 and recommender.py:870 enumerate users unpaged. Everyone past the first 1,000 falls through to the same generic popular-cards deck, forever, with no log.
self_model.py:130 is unpaged and one user already has 1,166 interactions — their map, clusters and mirror sentence are computed from an arbitrary 1,000-row slice right now. At 1,000 users this becomes routine for every engaged reader.
/v1/favorite is a textbook N+1. One card at a time, each spinning a fresh thread pool. Forty favourites ≈ 80 round-trips and ~7 seconds. The batch function already exists.
| Line | Today | At ~1,240 users | Note |
|---|---|---|---|
| Anthropic — nightly agent | ~$18 | ~$92 | 100% wasted until the one-line fix |
| Anthropic — go-deeper chat | low | ~$36 | No prompt caching — would cut ~70% |
| Anthropic — cluster naming | ~$2 | ~$10 | Working correctly |
| Railway | ~$5–10 | ~$25–45 | Single container |
| Supabase | $25 | $40–85 | Must move off Micro |
| OpenAI embeddings | pennies | ~$1 | Effectively free |
| Total | ~$55–65 | ~$210–270 | Excluding ad spend |
The constraint nobody has checked: Segment's free tier is 1,000 monthly tracked users. At 1,000 MAU you are exactly at the ceiling, and an overage silently drops events into both Amplitude and Customer.io at once — which will present as three unrelated bugs. Confirm the plan tier before the campaign starts.
cards10 promo codes? One minute, and it decides whether the gift flow ships at all./v1/paths, page the nightly user list, fix the 3-vs-10 agent gate. Plus three card_id indexes.