# Data processors — the single named list

**Status: AUTHORITATIVE.** Every external party that receives RetiFlo user data is named here, and
nowhere else. Created 2026-08-03 on legal advice: *"all external consumers of the data need to be
named and this could be done in one place and then referenced by all of the documents."*

**Rule: no document restates this list.** The privacy policy, terms of service, in-app consent text
and help content **link here**. A processor named in five places is a processor that will be added
in one and missed in four — which is exactly how the pre-2026-08-03 state arose, where the terms
named Anthropic for "AI insights" while three other features were also sending data to Anthropic.

> ⚠️ **The rule above is currently being broken, and it has already cost us.** [`privacy-policy.md`](privacy-policy.md)
> §5 does not link here — it **restates** the list as a table of its own. On 2026-08-29 the two were
> found to disagree: the privacy policy named **Hetzner**, and this file, which declares itself
> authoritative, did not. The drift ran in the worst possible direction, leaving the source of truth
> as the incomplete one.
>
> Hetzner is now in the table below. **The restatement in §5 is still there** and is the next thing
> to fix: either §5 links here like everything else, or this file stops claiming to be the only place
> the list is written. Two lists will disagree again — that is what this rule exists to say.

Companion documents:
- [`pii-classification.md`](pii-classification.md) — what counts as PII, and what is removed before
  data leaves the app. Generated from the code.
- [`privacy-policy.md`](privacy-policy.md) — the policy these two are addenda to.

---

## Active processors

| Processor | Purpose | What it receives | User control |
|---|---|---|---|
| **Anthropic** (Claude) | AI features — plan insights, the AI assistant, the Companion ask-line, and expense categorisation | The household's **plan data with PII removed** — see [`pii-classification.md`](pii-classification.md). Includes year-by-year projections, account balances and types, income and expense figures, and any question the household types. | **Opt-in.** Nothing is sent until consent is granted; withdrawable at any time in Settings. |
| **Stripe** | Payments and subscription billing | Name, email, and payment details, collected by Stripe directly. RetiFlo stores only the customer and subscription identifiers, never card details. | Required to hold a paid subscription. |
| **Resend** | Transactional and notification email | Email address and the message content — which may include plan figures where the notification is about them (e.g. a Roth conversion year-end reminder). | Notification preferences and one-click unsubscribe in Settings. |
| **Hetzner** | Hosting — the servers running the application and its database, located in **Ashburn, Virginia, United States** | **Everything you enter**, as it is held by the running service. The servers use full-disk encryption, and sensitive fields are separately encrypted by RetiFlo before they are written. Hetzner does not process your data for any purpose of its own; it provides the machines. | Inherent to using the service. |
| **Cloudflare** (R2 object storage) | Storing documents a household or their advisor uploads, and generated PDF reports | **Encrypted objects only.** Documents are encrypted by RetiFlo before they are stored, and the key is never sent with them — Cloudflare holds ciphertext it cannot read. File names, sizes and content types are held as object metadata. Objects are stored in the **western United States**. | Documents are optional; the household chooses what to upload, and deleting a document destroys the key that makes it readable. |

**Storage location, since §12 of the privacy policy turns on it.** Documents are held in the western
United States, and the application and database are in Ashburn, Virginia. **Everything stays in the
United States** — a document never leaves the country holding the database that describes it, so this
processor adds no new international transfer.

**One thing this row deliberately does not claim.** It does not say Cloudflare "cannot access your
documents" in the absolute. They hold ciphertext and not the key, which is true and is the point — but
a full compromise of RetiFlo's own environment would expose the key that unlocks it, and the row
should not imply otherwise.

> **Hetzner was added to the active list above on 2026-08-29 — an oversight, corrected on the owner's
> instruction.** It was surfaced by this proposal rather than by the vault itself: naming Cloudflare
> for holding *encrypted* objects made it untenable to leave unnamed the host holding the plaintext
> database. **That row is live and is not part of this proposal** — it stands whether or not the vault
> is approved.
>
> The general question it raises is still open with counsel (`questions-for-lawyers.md` § 19): what is
> the *rule* for what counts as a processor we must name — does it extend to a backup destination, or
> a CDN? A stated rule is more useful than a per-vendor answer, and would have caught this one.

### The four AI features, spelled out

Naming "Anthropic" once is not enough on its own, because the number of features behind that name has
grown and the documents did not keep up. All four send to the same processor under the same consent:

| Feature | Sends |
|---|---|
| Plan insights (`planComment.generateAiInsight`) | Section-level plan data for the page being commented on |
| AI assistant (`assistant.ask`) | Plan data reachable by its projection tools, including detailed figures |
| The Companion (`companion.ask`) | Year-by-year projection (income, expenses, taxes, portfolio, withdrawal rate) **and the question the household typed** |
| Expense categorisation | Transaction descriptions and merchant text from an imported statement |

All four route through one gateway (`packages/api/src/lib/llm/gateway.ts`), which applies PII
redaction on the way out and rehydration on the way back. `npm run audit:llm` fails the build if any
code constructs its own vendor client outside that gateway.

---

## Not currently active

Recording these matters as much as the active list: a processor that is switched back on without a
policy update is the failure this registry exists to prevent.

| Processor | Status |
|---|---|
| **Plaid** (account aggregation) | **Disabled.** The npm dependency is removed and `plaidClient.ts` is a stub; no account is linked and no data is sent. **Re-enabling it requires updating this file, `pii-classification.md`, and the privacy policy in the same change** — it would introduce a new category of data (institution credentials and transaction history) that the current policy does not describe. |
| **Cloudflare** (R2 object storage) | **Planned, not yet receiving data.** The account and buckets exist; the document vault that would use them is designed and not built ([`plans/document-vault.md`](../../plans/document-vault.md)). **Nothing is sent to Cloudflare today.** Named here in advance rather than on the day it goes live, because this file's rule is that a processor is recorded *before* it receives data, and the "not currently active" state is the honest way to do that. When the vault ships, the row below moves into the Active table above — the row is already written there under the proposal banner, so shipping is a move, not a drafting exercise. |

---

## Adding or removing a processor

1. Edit this file — it is the source, and the only place the list is written out.
2. If the change alters *what data leaves the app*, update
   [`pii-classification.md`](pii-classification.md) and the privacy policy's scope sentence.
3. Check that the referring documents still only **link** here rather than restating:
   `apps/web/src/app/(marketing)/privacy/page.tsx`, `apps/web/src/app/(marketing)/terms/page.tsx`,
   `docs/policies/privacy-policy.md`, `docs/policies/terms-of-service.md`,
   `apps/web/src/components/ui/AiConsentModal.tsx`,
   `apps/web/src/content/help/overview/data-privacy.mdx`.
4. Re-run `npm run generate:assistant-help` if help content changed, or the in-app assistant will
   keep answering privacy questions from the old text.
