Allin — infrastructure & how it all works
The single reference for every account, service, and piece of logic behind Allin — built for the move onto Allin's own Claude profile. It points to where each credential lives; it never contains the credentials themselves.
What Allin is & the stack at a glance
Allin is an iOS app for nervous-system / somatic "inner work" — a daily emotional check-in matches you to a guided audio "rewire" session, plus a personalised 10-step beliefs plan, an audio library, and a journal.
Client
iOS, SwiftUI. Repo Maelwi/allin-ios → local ~/Documents/allin-gh/allin-ios, branch main (commit aa3bd52). Bundle com.allinwellltd.allin, App Store ID 6754271583, current v2.3 (build 1) — archived, uploaded to App Store Connect 2026-08-26, not yet attached to a version/submitted (that step is Marie/Isabel's, in the ASC UI).
Backend
Supabase project wsxpmcbtcknewpiwudsc (us-east-1). Repo Maelwi/allin-backend, branch main (commit 094630f). Postgres + Auth (Sign in with Apple) + Storage (audio) + 9 live Edge Functions, versions confirmed live 2026-08-26 via supabase functions list: recommend-plan v39, revenuecat-webhook v7, recommend-audio v6, classify-affect v6, classify-crisis v5, delete-user-data v2, export-user-data v3, get-emotional-sessions v2, save-emotional-session v5. The last four are new this cycle — see the pseudonymization card below. Auto-deploys from main via GitHub Actions, per-function path-filtered (2026-08-14 fix).
Growth & lifecycle
Segment SDK → Amplitude + Customer.io (EU) + Meta SDK. RevenueCat/StoreKit for subscriptions. Deep links on Cloudflare Pages (link.join-allin.com).
Accounts & logins
Every service and the account it lives under. Ownership matters for the migration — note the mix of marie@join-allin.com, info@join-allin.com, and Isabel's Apple ID. Confirm the ⚠ rows during the move.
| Service | Account / login | Key identifiers | Where the secret lives |
|---|---|---|---|
| Apple Developer / App Store Connect | Account holder isabel.lewren@gmail.com | Allin Well Ltd · Team 2L65759584 · App ID 6754271583 | ASC API key .p8 + Xcode signing on the build Mac |
| GitHub | Maelwi (Marie); collaborator isabellewren | Repos allin-ios, allin-backend (both private) | gh CLI auth / SSH keys on the Mac; Actions secrets in the repo |
| Supabase | Org "Allin" (ixbtevgkpksybwxjptrd) — moved 2026-08-14 from Marie's personal account, where it sat alongside an unrelated project. Isabel is a full Owner. | Ref wsxpmcbtcknewpiwudsc · us-east-1 · Pro | Personal Access Token in macOS Keychain (confirmed it still works post-move); service-role key in Supabase secrets |
| Segment | Workspace "Join-Allin-Isabel" · info@join-allin.com | Source "Allin iOS" | Write key in iOS Info.plist + Supabase secret SEGMENT_WRITE_KEY. no erasure API access — see the GDPR wiki |
| Amplitude | marie@join-allin.com | Project 835236 · org join-allin-253655 · NA · Free plan | API key in Segment destination; AMPLITUDE_API_KEY/_SECRET_KEY added as Supabase secrets 2026-08-15 for account-deletion erasure, live-tested |
| Customer.io | info@join-allin.com | Workspace 216147 · Site ID 6f711…1664 · EU region | Track API key in Segment destination; APNs key for push; CUSTOMERIO_SITE_ID/_TRACK_API_KEY added as Supabase secrets 2026-08-15 for account-deletion erasure, live-tested |
| Meta / Facebook | ⚠ confirm Business Manager owner | FB App 2131466234361765 | Client token in iOS Info.plist |
| RevenueCat | ⚠ confirm login | Subscriptions (StoreKit) | Public SDK key in iOS; secret key in RC dashboard |
| Cloudflare | marie@join-allin.com | Pages: allin-links, allin-priorities, allin-onboarding · domain link.join-allin.com | wrangler auth on the Mac; API token in Cloudflare |
| Google Workspace | marie@ / info@join-allin.com | Email domain + Drive (audio masters, Docs/Sheets) | Google account passwords (password manager) |
| Website (Squarespace) | Isabel | join-allin.com | Squarespace login (Isabel) |
| Claude (this work) | Migrating m@umancstudio.com → marie@join-allin.com | Artifacts + connectors | Anthropic account login |
How Allin works today
The core loop and the routing that powers it — verified against the current code.
The daily loop
- Check in — emotion, situation, intensity + body, and the belief beneath.
- Rewire session — matched guided audio, streamed from Supabase Storage.
- Reflect — pre/post mood + a note, saved to the journal.
- Return — journal, streak & progress (non-punitive).
Check-in routing (deterministic gates + AI)
Intensity is the gate, emotion is the content, intent is the lane. A crisis scan intercepts early → region-aware support screen (no audio). "Express": intensity ≥ 8 and unpleasant → straight to a soothe audio. fixed Both gates confirmed at >= 8 in the current working tree — EmotionalCheckIn.swift:871 (the slider gate) and CheckinRouter.swift:188's separate mustSootheFirst gate. Neither is live yet: both ship in v2.3 (build 1), currently uploaded to App Store Connect but not yet submitted — the live App Store build still routes at >= 7.
Recommender
Corrected 2026-08-16: the critical path is NOT LLM-free — the live app's version-gate header activates the Anthropic/Claude composition path on every plan generation; Postgres full-text search is the fallback when Claude errors or fails validation, not the primary path. Tier 2 pgvector + Tier 3 shift-rating learning = sketched, not built.
10-step beliefs plan
Server-generated from an intake (areas + ratings + 4 free-text fields). Rating = life-area satisfaction (lower → more steps). Stored verbatim as intake_json; each step captures mood + shift_rating + feedback.
Architecture
iOS app
Core/AppView (routing), AuthenticationManager (Apple sign-in), SessionManager + SupabaseService (check-ins), BeliefsPlanManager/Service (plan), Onboarding/OnboardingFlow (8 steps), Core/DeepLink, Core/CrisisResources.
supabase-swift, analytics-swift (Segment), purchases-ios (RevenueCat), facebook-ios-sdk.
Backend / Supabase
recommend-plan (Deno/TS, v33) — JWT-auth; the live app's version-gate header activates a Claude/Anthropic composition path, with deterministic FTS as the fallback only (corrected 2026-08-16 — this card previously said "deterministic + FTS" as the primary path, which was wrong). Plus recommend-audio, classify-affect, classify-crisis, revenuecat-webhook — all live, all now tracked in the repo (found 2026-08-14: they existed live but weren't in git — see the repo/live drift flag below).
GitHub Actions on push to main: migrations → db push, audio metadata → re-seed. still an active landmine, re-confirmed 2026-08-26 The functions job still runs the same single hardcoded supabase functions deploy recommend-plan step it always has — a real per-function path-filtered fix exists (commit 53400bd, branch fix/recommend-plan-repo-drift) but was never merged into main, including in today's consolidation. Practically: pushing a change to any other function (e.g. delete-user-data) still only redeploys recommend-plan — the job goes green, giving false confidence, while the function that actually changed silently doesn't redeploy. All 9 live functions were verified live via direct supabase functions list today, but every one except recommend-plan reached production by manual CLI deploy, not CI. This is the same class of bug the "repo/live drift" box below already describes — it just never got fixed itself.
Bucket of guided-audio mp3s (public-read); only the onboarding video is bundled in the app.
Deep links
Cloudflare Pages allin-links → link.join-allin.com. Serves the AASA for universal links; non-iOS falls back (302) to join-allin.com. Paths: /home /checkin /plan /library /audio/* /journal /progress /profile /settings. Entitlement shipped in v2.2.
Push
APNs entitlement (aps-environment: production) + device token → Customer.io. verify APNs .p8 uploaded to CIO + production push confirmed on a prod build.
recommend-plan was live on Supabase (v31), but the allin-backend git repo's main branch still had an old pre-redesign version — nobody had ever committed the real one. The CI deploy step redeployed recommend-plan on any function change, so the next unrelated push would have silently overwritten the correct live recommender with the stale one. Pulled the real source via supabase functions download (the Management API's function-body endpoint returns a binary bundle, not source — learned that the hard way) and synced it plus the 3 other undocumented live functions into the repo — that part is genuinely done. The per-function CI filter meant to close the actual landmine was written the same day (commit 53400bd) but sits on branch fix/recommend-plan-repo-drift, never merged — including in today's consolidation onto main. main's CI still hardcodes one deploy recommend-plan step regardless of which function actually changed. Standing rule this created: never assume the repo reflects what's actually live — verify against the live project before touching or redeploying anything. That rule is exactly why this was caught again today instead of being taken on faith.
Data model
Public schema. Everything that keys on a user cascades on delete, so account deletion fully erases the database (verified). Free-text fields marked special-category are mental-health data.
| Table | Holds | Sensitivity |
|---|---|---|
| emotional_sessions | Check-ins: emotion, body, trigger, belief, memory, desired/new belief, reframe, pre/post mood, reflection_notes. Pseudonymized + encrypted, live 2026-08-25. | special-category |
| beliefs_plans | 10-step plan + full intake (intake_json), llm_reasoning. Pseudonymized, live. | special-category |
| beliefs_plan_steps | Per-step mood, shift_rating, feedback_text. Pseudonymized, live. | special-category |
| private.user_pseudonyms | Real auth.users.id → random pseudonym UUID. Never exposed via PostgREST — the only table that can re-link the tables above to a real identity. | special-category (join key) |
| user_entitlements | subscription_status, RevenueCat id, consent_granted_at (unused) | personal |
| users / auth.users | Account id + email (Apple) | personal |
| audio_library · app_config · checkin_config | Content + config (no personal data) | non-personal |
RPCs: delete_user() (cascading erase — fixed 2026-08-25 to rely purely on cascade post-Stage-3, no longer does a doomed explicit emotional_sessions delete by user_id), search_audio_library(), get_or_create_pseudonym(), read-only my_pseudonym() / write-capable my_pseudonym_for_write() (split 2026-08-25 to fix a first-insert RLS bug — see below). Index: emotional_sessions_user_ts_idx added 2026-08-16 — this was the busiest table in the schema with no index on user_id; confirmed live via EXPLAIN that the planner now uses it.
Pseudonymization Stages 1–3 + encryption at rest — shipped this cycle
Both were previously flagged as "engineering mostly done, not yet shipped." As of commit 094630f on backend main, both are live and coordinated with iOS commit aa3bd52 (v2.3 build 1). Encryption alone only protects against a stolen backup; pseudonymization is what removes the identity-join risk from a real breach of the database itself — both were shipped together deliberately, not staged separately.
What changed
All three special-category tables had their direct user_id foreign key dropped and replaced with pseudonym (a random UUID, cascade-linked via private.user_pseudonyms). Five plaintext columns (trigger, belief, memory, desired_belief, new_belief) are now AES-256-GCM encrypted at rest (_shared/session-encryption.ts), key held only in the SESSION_ENCRYPTION_KEY edge-function secret, never in Postgres.
New edge functions
save-emotional-session and get-emotional-sessions replace the iOS app's old direct-table Supabase calls — the client no longer knows the real user_id at all for these tables, only the edge function (via the caller's JWT) does. export-user-data updated to resolve pseudonym once via RPC for its queries.
Bugs found & fixed getting here
A STABLE-marked my_pseudonym() cached a stale NULL within a single INSERT statement, failing RLS for every brand-new user's first check-in — split into a non-STABLE read function and a separate get-or-create write function. Separately, the critical one: Swift's SupabaseEmotionalSession.userId / BeliefsPlan.userId were non-optional — since Stage 3 removed user_id from every API response, every check-in save/read and plan generation would have thrown a decode error on live main. Found by an audit agent, not by earlier curl-only testing; fixed by making both fields UUID? and verified via an isolated Swift decode of a real captured API response before merging.
Analytics & lifecycle pipe
iOS (Segment SDK) + backend edge functions → Segment source "Allin iOS" → fan-out. Verified live in code.
| Destination | Purpose | State |
|---|---|---|
| Amplitude (835236) | Product analytics, "Allin — Core" dashboard | live · free plan |
| Customer.io (216147, EU) | Email + push lifecycle | pipe live, campaigns not composed/firing |
| Meta SDK | Ad attribution / conversions | live (since v1.8) |
| RevenueCat | Subscription events | live |
GDPR The pipe initialises at launch, before consent — see the GDPR plan (Phase 2) for gating it. anonymisation Today the app sends a persistent id AND real email to Segment on sign-in, forwarded to both Amplitude and Customer.io. Customer.io needs the email (it sends mail); Amplitude doesn't. Fix is a one-step Segment Destination Filter, not yet applied — see the GDPR wiki.
erasure Account deletion propagation to processors: Amplitude + Customer.io done and live-tested; Segment blocked — no accessible Public API token in this workspace, a support request to Segment is drafted. Full status in the GDPR wiki.
Push tokens now bypass Segment entirely — Customer.io's own SDK
Root cause found this cycle: Segment's Customer.io destination only reads a push token off context.device.token on the "Application Installed" event, which fires exactly once per device at the very first launch ever. registerForRemoteNotifications() needs a real network round-trip to Apple, which is structurally slower than that one-shot event — so on a fresh install the token is essentially always still unknown when it fires, and there's no second chance. Fix: CustomerIOPushClient.swift (new file) uses Customer.io's own iOS SDK (customerio-ios via SPM, products DataPipelines + MessagingPushAPN) purely for push-token registration — it registers the token directly against Customer.io's API the moment APNs returns it, no dependency on Segment's install event. Segment still owns every identify/track/screen call, unchanged — this client is deliberately narrow so it can't create a second tracking pipeline. Wired into AllinApp.swift via CioAppDelegateWrapper, with identify() called at every site AnalyticsClient.shared.identify is, and reset() added at sign-out/delete-account (a gap found by audit — without it, a second person signing in on the same device would inherit the previous person's push token attribution). Confirmed working end-to-end via the Customer.io dashboard: source connected, profile exists, clean event stream. The one remaining piece (an actual device token showing up) is blocked on notification permission not yet being granted on the test device — not a code issue.
Tracking audit — funnel gaps closed this cycle
Triggered by a full parallel-agent audit of the tracking/analytics/CRM system end to end. New events, all routed through the existing Segment pipe (no second pipeline): sign_up_completed (fires exactly once per real account, distinct from sign_in_succeeded which fires every sign-in — closes a real gap, there was previously no way to see raw signup volume separate from onboarding completion or repeat sign-ins), reflection_completed (mood_shift, had_note, note_length — never raw note text), session_milestone_reached (fires at 1/2/3/10 completed sessions), crisis_support_shown (tagged by source: severity_gate vs checkin_router), audio_session_exited (audio_slug, audio_title, percent_complete — the "how far did they listen before bailing" gap), plus screen() calls added to Dashboard and Settings. All consent-gated — see AnalyticsClient.identify/track/screen below.
v2.3 (build 1) — everything else in this release
Branch fix/apple-signin-email-scope merged to iOS main 2026-08-26. Named for its original bug but grew to carry the whole GDPR/consent/crisis-safety/tracking cycle above, plus:
Apple Sign-In email capture
Root fix: request.requestedScopes = [.fullName, .email] now set on every signInWithApple() call, and the raw identity-token JWT is passed to Supabase's signInWithIdToken rather than reading credential.email client-side. Apple quirk: the email/name grant locks in permanently at an Apple ID's first-ever authorization with the app — can't be retroactively granted later, so accounts created before this fix (including test accounts) can't demonstrate it without a full "Stop Using Apple ID" reset.
Consent-gated analytics, actually enforced
AnalyticsClient's identify()/track()/screen() now hard-gate on hasAnalyticsConsent — previously, revoking consent mid-session didn't stop tracking because reset() never disarmed the client. Three independent consent toggles (reminders / analytics / advertising) with a Settings → Privacy screen to revisit them; consent is cleared on account deletion specifically (not on plain sign-out, so a returning same-account user keeps their prior consent).
Account deletion hardened
deleteAccount() no longer swallows a failed delete-user-data call and proceeds as if it worked — a failure now surfaces an error and stops, instead of a user believing they'd exercised erasure while their data was still live.
Two crisis-safety race fixes
Same bug class, two call sites: EmotionalCheckIn.swift's CheckinHeader back button, and AppView.swift's outer check-in back-navigation (reachable via assistive-technology activation bypassing a visual-only overlay), could each discard an in-flight true-positive crisis classification before it was checked. Both fixed by reusing existing state flags — no new state, no visual change.
Docs & wikis we've built
Everything produced for Allin, and where it lives. The four wikis also have durable HTML copies in ~/Documents/allin-wikis/ ready to re-publish from the new profile.
~/Downloads/Allin-Knowledge-Base.pdf.m@umancstudio.com Drive (migrate ownership to join-allin.com).allin_* files~/.claude/…/memory/ (indexed by allin_index.md).Connectors & a caveat
What this Claude environment can reach for Allin — and one important gotcha to fix on the new profile.
| Connector | Use for Allin | Note |
|---|---|---|
| Supabase (MCP) | Read/inspect the prod DB, run SQL | privileged — bypasses the redacted views; the GDPR plan restricts this |
| Google Drive (MCP) | Create/read Docs & Sheets | Under m@umancstudio.com today |
| Amplitude (MCP) | — | wrong account — authed to the Insights org, cannot see Allin's project. Re-auth to marie@join-allin.com on the new profile. |
| Cloudflare / wrangler | Deploy the public wikis + deep-link redirects | CLI auth under marie@join-allin.com |
Migrating to Allin's Claude profile
You're moving this work from m@umancstudio.com to marie@join-allin.com. Here's exactly how each piece travels — and how to "move" a wiki.
How to move a wiki (artifact)
Artifacts are tied to the Claude account that published them — they don't transfer between accounts, and the link stays owned by the old account. So you re-publish, you don't "move":
- The HTML for all four wikis is saved in
~/Documents/allin-wikis/(done). - Sign in to Claude as
marie@join-allin.comand open a session with this same folder. - Ask it to publish each file as an artifact → it mints fresh URLs owned by the new account.
- Update any cross-links, then retire the old artifact URLs.
Shortcut that needs no migration: the public Cloudflare copies (allin-priorities.pages.dev, allin-onboarding.pages.dev) live on Cloudflare, not Claude — they already work for anyone, on any account. Keep the Cloudflare login and they carry over untouched.
| Asset | How it moves |
|---|---|
| Wikis (artifacts) | Re-publish the HTML in ~/Documents/allin-wikis/ from the new profile (above) |
| Public wiki URLs | No move needed — they're on Cloudflare (marie@join-allin.com) |
| Google Doc & Sheet | Transfer ownership from m@umancstudio.com to a join-allin.com account in Drive |
Claude memory (allin_*) | Local files — copy the memory folder to the machine/profile you'll run the new Claude on |
| Amplitude MCP | Re-authenticate the connector to marie@join-allin.com so it can finally see Allin's project |
| Code, Supabase, Cloudflare, App Store | Nothing to migrate — they're their own accounts; the new Claude just needs the same CLI auth on the machine |