Data Flow — Chat Attachments (ADR-0043 Phase 1)
- Status: Draft (companion to DPIA + DPA template)
- Date: 2026-05-09
- Purpose: regulator-facing diagram of every region a byte may visit. Auditors get a single page; engineering keeps it accurate.
1. End-to-end flow (image upload, vision-enabled tenant)
Section titled “1. End-to-end flow (image upload, vision-enabled tenant)”[Visitor browser, $any region] │ │ HTTPS multipart POST (TLS 1.3) ↓[Cloudflare Worker — apps/app — closest CF edge] │ ├── 1. Validate session: tenant slug + visitor cookie ├── 2. Magic-byte sniff: PNG/JPEG/WebP/HEIC head bytes ├── 3. Edge classifier (CF Workers AI — global edge): │ "is this an ID card / face / credit card?" │ ├─ "yes" + tenant policy = block → reject 422 │ └─ "yes" + tenant policy = warn → proceed, mark sensitive ├── 4. Rate-limit check (KV_SESSIONS — APAC primary) ├── 5. Quota check (D1 query — APAC primary) ├── 6. R2 PUT (Cloudflare R2 — APAC primary, Singapore) │ Object key: attachments/{tenantId}/{visitorId}/{nanoid}.{ext} │ Encrypted at rest: AES-256, CF-managed keys │ Custom metadata: {tenantId, visitorId, sensitive: bool} ├── 7. D1 row insert (apps/app D1 — APAC primary) │ messages.attachments_json += {id, url, type, size, hashedName} └── 8. Audit log entry (D1 — same row) chat.attachment.upload, no PII in payload │ │ HTTP 200 response with {id, url} to widget ↓[Visitor widget] │ │ Visitor sends chat turn referencing attachment id ↓[Cloudflare Worker — apps/app] │ ├── If sensitive flag set → skip vision, route to agent inbox only └── Else: pre-flight to vision │ │ HTTPS POST to AI Gateway (CF) → Anthropic ↓ [Anthropic Claude Haiku 4.5 — US-WEST-2 by default, EU available on Enterprise tier] │ ├── Image content block + text content block ├── Inference happens entirely in Anthropic's infrastructure ├── ZDR contract: input bytes NOT retained beyond inference └── Returns text answer │ ↓ [Cloudflare Worker] │ ├── Persist text answer to D1 messages └── Stream back to visitor2. Where bytes live, by lifecycle stage
Section titled “2. Where bytes live, by lifecycle stage”| Stage | Location(s) | Encryption | Retention |
|---|---|---|---|
| In-flight upload | TLS 1.3 over public internet → CF edge | TLS 1.3 in transit | None — transient |
| Edge classifier | CF Workers AI — global edge | TLS internal | None — stateless inference |
| Rest in R2 | CF R2 — APAC (Singapore) | AES-256 server-side | Until tenant-defined retention or visitor erasure |
| In Anthropic memory | US-WEST-2 (or EU on Enterprise) | TLS internal + Anthropic infrastructure | None under ZDR; up to 30 days without ZDR |
| In D1 (metadata only) | CF D1 — APAC | AES-256 | Same as R2 |
| In audit log | CF D1 — APAC | AES-256 | Per ADR-0023; tombstone after PII erasure |
| In Sentry breadcrumbs | Sentry EU (project default) | AES-256 + EU residency | 90 days; bytes scrubbed via beforeSend |
3. Where bytes do NOT live
Section titled “3. Where bytes do NOT live”Documented for auditors who ask the negative question:
- Not in browser localStorage / IndexedDB beyond the live session
- Not in Cohere (text-only reranker; never receives image bytes)
- Not in Stripe (billing only; no visitor data)
- Not in Resend (we use CF Email Sending — see ADR-0042 — and email never carries attachment bytes)
- Not in CDN public cache (
Cache-Control: privatefor attachments; only avatars get public+immutable) - Not in CI logs (test fixtures use synthetic images, not real visitor data)
- Not in backups beyond retention window (D1 daily backups inherit retention; R2 objects in scope of erasure cascade)
4. Data minimisation in flight
Section titled “4. Data minimisation in flight”| Field | Visitor sees / sends | Server stores | AI provider sees |
|---|---|---|---|
| Original filename | Yes (widget shows it) | Hash + sanitised display name | Original filename if image is sent (Anthropic API takes filename in metadata; we send attachment.{ext}) |
| Source IP | — (browser hides own IP) | Yes (audit log; geolocated to country) | No |
| Browser UA | — | Yes (audit log) | No |
| EXIF GPS in image | Yes (browser) | Stripped server-side before R2 PUT (TODO: phase 1 implementation must include this) | No (post-stripping) |
| Image bytes | Yes | Yes (R2) | Yes if vision-enabled and not sensitive-blocked |
5. Per-tenant variance
Section titled “5. Per-tenant variance”Defaults derived from plan; tenant admin overrides where allowed.
| Plan | AI vision default | Sensitive-data policy | Retention default |
|---|---|---|---|
| Free | on | warn | 30 days |
| Team | on | warn | 90 days |
| Business | configurable, default on | block | 1 year, configurable to 7 years |
| Enterprise | configurable, default off | block | per-conversation, max 7 years, legal-hold flag |
6. Cross-border legal vehicle
Section titled “6. Cross-border legal vehicle”| Transfer | Mechanism |
|---|---|
| Visitor (any) → CF edge (any) | None required (TLS in transit only; edge processing is part of the service) |
| CF edge → R2 APAC (Singapore) | CF DPA + SCCs (PDPA §28 equivalent + GDPR Chapter V) |
| CF edge → Anthropic US-WEST | Anthropic Enterprise + ZDR addendum + SCCs |
| CF edge → Sentry EU | EU SCCs + UK addendum |
| EU visitor → US transit | SCCs as above; surfaced to visitor in privacy notice |
| Thai visitor → all transits | DPA discloses recipients; PDPA §28 SCC-equivalent in DPA |
7. Where this document lives in the regulator-facing pack
Section titled “7. Where this document lives in the regulator-facing pack”When a regulator (PDPC TH, EU DPA) requests documentation on processing:
- DPA (
docs/compliance/dpa-template.md— instantiated per customer) — defines processor / controller relationship - DPIA (
docs/compliance/dpia-attachments.md) — risk analysis - This document — visual + tabular flow proving the DPIA is implemented
- Audit log — proves it operated as documented
The four together form a self-contained answer to “where does this image go and why is that allowed.”
8. Update triggers
Section titled “8. Update triggers”This document MUST be updated within 7 days when:
- A new sub-processor is engaged
- A region changes for any sub-processor
- A new attachment-related route or endpoint is added
- Retention policy changes for any plan tier
- Tenant-side knobs are added or modified
Authoritative source for changes: ADR-0043 + sub-processor change log in docs/compliance/dpa-template.md §6.