Allin — internal
Enter the password to continue.
Allin — GDPR

Allin — GDPR

The single page for this workstream: the full audit, every build and why it happened, what's actively in progress, and what's still an open decision — split by who it's waiting on. Nothing here is "not being worked on"; anything not finished is either mid-build or waiting on a specific person's call, and that's marked either way.

How this list was built

So you can judge whether it's actually complete, not just trust it
This wasn't brainstormed — it comes from working through the standard GDPR obligation categories one at a time and checking each against Allin's actual live code and schema, not the intended design: lawful basis (what allows processing check-in/health-adjacent data at all — Art. 9 explicit consent), transparency (Arts. 12–14, the privacy policy has to be true), data subject rights (Arts. 15/17/20 — access, erasure, portability), storage limitation (Art. 5(1)(e) — a defined retention period), security (Art. 32 — pseudonymization/encryption as named example measures), accountability (Art. 30 ROPA, Art. 35 DPIA for high-risk processing), processor agreements (Art. 28 — a DPA per third party, cross-referenced against every processor actually found in the data-flow mapping), plus the UK-specific pieces layered on top (ICO registration, the Children's Code given the product's audience isn't age-controlled). Several items only surfaced because the live code was checked directly rather than assumed — the consent-recording function that didn't exist, Anthropic as an undisclosed processor, the repo/live drift on recommend-plan. That's the actual basis for calling this reasonably complete: systematic obligation-by-obligation coverage, verified against what's really running. Honest limit: this is a technical/product review, not a solicitor's sign-off — the borderline calls (age-gate, ICO specifics) are worth a real lawyer confirming before treating this as final.

Everything done, and why

the full history behind where this stands today

Consent redesigned into three separate, specific toggles

The foundation everything since builds on
Done
The starting point for this whole workstream. The old flow was one low-friction button — "allow tracking and reminders" or "skip" — covering multiple different purposes (analytics, advertising, reminders) with a single yes/no. GDPR requires consent to be specific: each distinct purpose needs its own separate, informed, freely-given choice, not one blanket switch. That's why this became a genuine 3-decision screen: independent toggles for reminders, analytics ("improve the app"), and advertising, each gettable and revocable on its own — plus a Settings → Privacy screen so someone can come back and change their mind later, since GDPR also requires withdrawal to be as easy as granting. One thing still genuinely unresolved from this redesign: there's no separate decline/skip button — leaving every toggle off and tapping "continue" is the decline path, so someone who says no to everything sees the exact same button, with the exact same word, as someone who said yes to everything. Worth Isabel's read on whether "continue" reads neutral enough either way.
See it

We want to make this practice better,

your choice, for each of these.

🔔
reminders
📈
improve the app
📣
ads
continue
Approximation built from the app's real colors/layout, not a screenshot — full version + editable copy at allin-copy-review.pages.dev. Source: Allin/Home/ConsentSheet.swift

Analytics/advertising actually gated behind that consent

Done
Built alongside the consent redesign: AnalyticsClient/MetaAdsClient now check the stored consent state before sending anything at all, rather than sending first and hoping someone opts out later. This is the part that actually protects people day to day — the audit trail (below) is about proving it happened, this is about it actually not happening when someone says no.
See it
Allin/Core/AnalyticsClient.swift · Allin/Core/MetaAdsClient.swift

Amplitude stopped receiving email

Done — live
Amplitude needs a persistent reference to a person for behavioral analysis, but never needed their actual email — sending it anyway was excess data under the minimization principle (Art. 5(1)(c)), and was flagged as the single most urgent, fastest fix across every audit done this project. Fixed by removing the Segment mapping that forwarded email as a User Property to Amplitude, then independently re-verified against the app's real analytics code (not just the config screen) to confirm it's actually gone, not just configured-to-be-gone. The only item on this whole page that's both fixed and confirmed live in production.
See it
Impact: check Amplitude → User Properties on any new user going forward — no email field. Verified against Allin/Core/AnalyticsClient.swift (the identify() call).

Account deletion, hardened

Done
Found a real problem while reviewing the erasure flow: if deletion failed partway, the failure was silently swallowed and the app proceeded exactly as if it had succeeded — meaning someone could believe they'd exercised their right to erasure while their account and data were still fully live. Pretending success is arguably worse than a visible error, since it removes the person's ability to even know something went wrong and try again. Fixed to surface the failure and stop instead of continuing as if it worked. Also fixed alongside this: consent is per-account, not per-device, so it's now explicitly cleared on delete (not on a plain sign-out, which should let a returning same-account person keep their prior consent) — otherwise a deleted account's consent could silently carry over to whoever signs up next on that same device.
See it
Allin/Core/AuthenticationManager.swiftdeleteAccount(), line 95

Erasure fan-out to third parties (delete-user-data)

Built, not yet deployed
In progress
Account deletion previously only removed Allin's own Supabase rows — it never told Segment, Amplitude, or Customer.io, so a "deleted" person's data kept living indefinitely in every third-party processor. This function fans erasure requests out to all three in parallel, then calls the existing delete_user() database function. A processor failure is logged but deliberately non-fatal — trapping someone in a "we couldn't delete your account" loop because a third-party API had a bad minute would be a worse GDPR posture than proceeding, not a better one, and none of these processors delete synchronously anyway (their own stated SLAs are "within 30 days," not instant). Meta is deliberately not called here — they don't expose an API for requesting erasure of an arbitrary user, only the reverse (they notify you if someone deletes their Facebook account); Allin's actual control over Meta data is upstream, in what it collects and the consent gate already in place.
See it
allin-backend · branch combined-fixes-2026-08-15 · supabase/functions/delete-user-data/index.ts

Consent audit trail — a real gap, found and fixed

In progress
Discovered while reviewing the consent screen's actual server-side behavior: the function the app calls every time someone completes the consent screen, record_consent_granted, never existed anywhere in the backend — not in a migration, not deployed. The call was wrapped in a silent try?, so it had been failing with zero error since the granular screen shipped. The practical protection (not sending events without consent) was fine, enforced separately client-side — what was missing was the ability to prove consent was ever given, which Art. 7(1) specifically requires. Built the missing table and both functions the app needed (one for the initial grant, one for single-toggle changes from Settings, which had no server call of any kind before this), and wired the app to actually call them.
See it
allin-backend · branch feat/gdpr-consent-record · supabase/migrations/20260817000000_user_consents.sql
Allin/Home/SettingsView.swift · Allin/Core/AppView.swift (the two call sites)

Pseudonymization, Stage 1 of 3

In progress
The redacted-views approach below only protects when someone logs in through the restricted role — it does nothing to stop the owner or service-role login from reading identity and content together directly. Getting to "even a full-privilege login sees a random token, not a person" needs the account id actually separated from the content, which is a bigger, staged piece of work. Stage 1 (built): an additive schema — a locked-down mapping table, a trigger that stamps every new check-in with a random token automatically, and RLS policies that accept the old check or the new one, so nothing that works today can break. Stages 2 (backfilling existing rows) and 3 (dropping the real account id column) are the two steps that actually deliver the protection — see the open decision below.
See it
allin-backend · branch feat/gdpr-layer2-pseudonymize · supabase/migrations/20260816000000_layer2_pseudonyms_stage1.sql

Redacted admin views + a restricted login role

In progress
The fast, safe mitigation to do while pseudonymization is properly staged: three SQL views exposing only coarse metadata (a truncated id, mood-shift direction, never the free text or an email) plus a low-privilege login that can see only those views, not the underlying tables. Reduces the everyday risk of someone glancing at a dashboard and seeing a name next to a private note — it just doesn't stop a privileged/owner login, which is exactly what pseudonymization is for. Purely additive, reviewed against the live schema for column accuracy.
See it
~/Documents/allin-gdpr/REDACTED_VIEWS.sql

Right to export + retention identification

In progress
export-user-data mirrors delete-user-data's pattern — returns everything Allin's own tables hold for the calling user as JSON, scoped to what Allin's systems hold directly (not what third parties separately retain, a stated choice not a hidden one). dormant_accounts() is retention policy Stage 1 — identification only, no deletion or notification — finds accounts inactive past a cutoff (defaulted to 24 months, a proposal not a decision). Both are backend-complete; what's left for each is listed under open decisions below.
See it
allin-backend · branch feat/gdpr-export-retention · supabase/functions/export-user-data/index.ts
supabase/migrations/20260817120000_dormant_accounts.sql (same branch)

Anthropic discovered as an undisclosed processor

Done — corrected the record
recommend-plan had been documented as "100% Postgres full-text search, no LLM call" — that was wrong. The live app activates a Claude/Anthropic composition path on every plan generation; full-text search is the fallback only. That makes Anthropic a real processor, receiving check-in and plan-intake text, that wasn't previously disclosed anywhere. Added to the DPIA, ROPA, and the privacy policy draft the moment this was found — and a signed DPA with Anthropic became the single highest-priority open item as a direct result (see below).
See it
~/Documents/allin-gdpr/DPIA.md · ROPA.md — both updated with Anthropic listed

Privacy Policy corrected — including catching its own mistake

In progress
The live policy states things that are false: "Allin does not actively share personal data with third parties" and "we do not collect health data" — both wrong, given real behavior (Segment→Amplitude/Customer.io/Meta, and the check-in flow literally is special-category data collection). A corrected draft was written to fix this — and a few hours later, that draft was itself found saying check-in text is "never sent to any other outside provider," which stopped being true the moment Anthropic was discovered. Corrected the correction openly rather than quietly editing it, since a document written to fix false claims shouldn't ship containing one of its own. The text is ready; publishing it is a Squarespace action for Isabel, needing both her and Marie's sign-off first (see open decisions).
See it
Full corrected replacement text: Privacy Policy & Terms draft

DPIA, ROPA, privacy manifest

Done
The foundational accountability paperwork Art. 30/35 require for processing this kind of data at all — data protection impact assessment and record of processing activities, both complete and kept current as new processors (Anthropic) were found. The iOS privacy manifest was corrected to match actual ATT-gated tracking behavior rather than a stale description.
See it
~/Documents/allin-gdpr/DPIA.md · ROPA.md · Allin/PrivacyInfo.xcprivacy

A standing rule for any future AI feature that reads someone's own history

Done — design principle, not a build
Came up when a future feature was confirmed (something like "we've mapped your subconscious," reading a person's own check-in history through a model). Worked out that this needs no identifier sent to the model in either direction, if built as: gather the person's own history → send only the content in one request → get the result back → save it against their record. Identity only ever matters on Allin's own side of that call. If a future version ever needs the model to hold memory across separate calls, the correlation key must be the pseudonym, never the real account id. Written down now so it can't be missed when that feature actually gets built.

Apple's DPA

Done
Already covered by the existing Apple Developer Program License Agreement — the one processor on this whole list that needed no separate action.

Open decisions — legally required

real obligations under GDPR / UK data protection law

Nothing above is actually live yet

Everything is sitting on branches
Marie + Isabel — sign off, then push
The consent screen and the consent audit trail are built and safe to ship, but none of it satisfies anything legally until it's actually protecting a real user. Being built isn't the same as being compliant.

Signed DPAs with every processor

Anthropic, Supabase, Amplitude, Customer.io, Meta, RevenueCat
Legal requirementMarie — in progress via Claude Desktop
GDPR Art. 28 requires a data processing agreement with anyone processing personal data on your behalf — not optional, not scaled by sensitivity. Highest urgency: Anthropic (newest-confirmed, real check-in/plan text) and Supabase (holds every table directly). Most of the rest likely have a self-serve DPA acceptance in account settings — that's what's being checked right now, platform by platform, with Anthropic sequenced last since it's the one most likely to need an actual written request.

ICO registration

Not yet confirmed either way
Legal requirementIsabel
UK data controllers must register with the ICO and pay the annual fee, with narrow exemptions that don't apply here. Unlike most GDPR obligations, this is trivially checkable by anyone via the public register — one of the more commonly enforced gaps precisely because it's so easy to spot. The fee is small (typically the lowest tier, roughly £40–60/year for a company this size).

Publish the corrected Privacy Policy

Text is ready; the live one still makes false statements
Legal requirementIsabel to publish, both to sign off
The most visible item here — unlike the backend gaps, this one is a public page anyone (a user, a journalist, a regulator) can read right now and see is false. Squarespace page is being prepped to receive the text; publishing itself waits on both sign-offs.

Right to data export — needs an app entry point

Legal requirementIsabel + Marie — place the button
Backend is built. What's missing is somewhere in the app for someone to actually trigger it — a "download my data" entry point in Settings, most likely next to delete-account. Small product/design decision, not a backend one.

Confirm the retention dormancy window

Legal requirementMarie — confirm the number
Art. 5(1)(e) requires a defined retention period. Recommendation: keep data for as long as an account is active (a fixed short period would break the product's own longitudinal value), with an inactivity sweep as the actual cutoff — dormant_accounts() is built and defaults to 24 months, a proposal not a decision. What happens to a dormant account once found (delete outright vs. warn first) is a second, separate decision.

Pick the age-gate approach

Terms state 16+; nothing enforces it
Legal exposureIsabel + Marie — pick the approach
The UK Children's Code applies to any service children are likely to reach, not just ones aimed at them — and paid ads mean the real audience age isn't controlled. Three options ranked by friction: (1) rely on the App Store's own age rating alone — weakest; (2) self-declared birthdate at signup, hard-block under-16 — low friction, demonstrates a genuine attempt; (3) full parental-consent flow for 13–15 — most engineering, appropriate only if the real audience skews younger than assumed. Lean: option 2. The one item here that's a genuine product/legal call, not a pure build.

Open decisions — recommended

GDPR names these as example measures, not hard requirements on their own

Pseudonymization — go to Stage 3, or stop at Stage 1

RecommendedMarie — schedule, not urgent
Stage 1 alone protects nothing — a pseudonym sitting next to the real account id can be joined straight back by anyone with database access. (A) Go to Stage 3 (drop the account id) — the only path that delivers real protection, but a one-way column drop needing a verified backfill and a backup first, on its own watched window. (B) Stop at Stage 1 — nothing more to build, but today's actual risk for this threat stays unchanged. Neither is urgent.

Encryption at rest

RecommendedMarie — architecture decision
Pseudonymization hides who; encryption hides what — you'd want both. Blocker is architectural, not legal: the key can't live inside Postgres and still be genuinely outside a privileged login's reach, so encryption has to move into an edge function, meaning the app can't read/write check-ins directly anymore — real latency and offline-behavior costs to weigh first.

Apply the redacted admin views

RecommendedMarie's go to apply
Built, reviewed against the live schema, purely additive. Just needs an explicit yes since it's a live-database action.

Different legal area entirely

not GDPR, but real

Marketing claims — 2 items need wording

Advertising standards / consumer protection, not data protection
Different domainIsabel
Everything else on the claims register is sourced and safe; two claims need Isabel's exact wording call.

Who does what — pulled out on its own

I

Isabel

ICO registration · Privacy Policy — review, sign off, publish · marketing claims wording (2 items) · age-gate approach (with Marie) · sign off on the consent screen + release branch (with Marie) · where the data-export button goes (with Marie)
M

Marie

DPA checks, in progress via Claude Desktop · confirm the retention dormancy number · encryption architecture decision · schedule pseudonymization Stage 2/3 · the go-ahead to apply the redacted admin views · age-gate approach (with Isabel) · sign off on the consent screen + release branch (with Isabel)