# Data Processing Record (GDPR Article 30)

## Purpose & Audience

This document constitutes the Record of Processing Activities (ROPA) required by GDPR Article 30 for the Coda platform. It inventories all personal data processed by the platform, describes the purpose, legal basis, storage location, retention, and technical safeguards for each data category. It is intended for DPOs, compliance officers, and auditors. For the security architecture, see [security.md](security.md).

**Data Controller:** The tenant organization (customer)
**Data Processor:** The Orchard (operating the Coda platform)
**Sub-Processors:** AWS (infrastructure), Auth0 (authentication), Sentry (error monitoring), Datadog (observability)

**Last reviewed:** 2026-04-10

---

## Categories of Data Subjects

| Category         | Description                                                   |
| ---------------- | ------------------------------------------------------------- |
| **Tenant users** | Employees of customer organizations who use the Coda platform |
| **Super admins** | Internal Orchard staff with cross-tenant platform access      |

---

## Processing Activities

### 1. User Identity & Authentication

| Field                | Details                                                                                                                                                                                                                       |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | User identity                                                                                                                                                                                                                 |
| **Personal data**    | Orchard identity ID (UUID), given name                                                                                                                                                                                        |
| **Source**           | Auth0 JWT token (`grass.theorchard.com/identity` claim)                                                                                                                                                                       |
| **Purpose**          | Authenticate users; associate conversations and data with the correct user                                                                                                                                                    |
| **Legal basis**      | Contractual necessity (Art. 6(1)(b)) — required to provide the service                                                                                                                                                        |
| **Storage location** | Aurora MySQL (`users` table)                                                                                                                                                                                                  |
| **Protection**       | Identity ID: HMAC-SHA256 hash for lookup (`identityHash`), AES-256-GCM encrypted copy (`identityEncrypted`). Raw ID never stored in plaintext. Given name: extracted at request time from JWT, not persisted in DB.           |
| **Retention**        | Until user requests deletion or tenant deactivation. No automated TTL.                                                                                                                                                        |
| **Access**           | Application via HMAC lookup. Privileged decryption requires `CODA_DB_IDENTITY_AES_KEY`.                                                                                                                                       |
| **Deletion**         | `User` row deletion cascades through `TenantUser`, `UserRole`, `UserPermission`. **Note:** `onDelete: Cascade` is **not set** on `OAuthConnection` or `Chat` — requires manual deletion before `User` delete or a schema fix. |
| **Schema reference** | `packages/db/prisma/schema.prisma:20-57`                                                                                                                                                                                      |

### 2. Tenant Membership

| Field                | Details                                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | Organizational membership                                                                                           |
| **Personal data**    | User ID (FK), tenant ID (FK), membership status, jurisdiction, department, invitation metadata                      |
| **Source**           | Platform service (tenant admin actions, SCIM provisioning in Phase 2)                                               |
| **Purpose**          | Associate users with customer organizations; enforce tenant-scoped permissions                                      |
| **Legal basis**      | Contractual necessity (Art. 6(1)(b))                                                                                |
| **Storage location** | Aurora MySQL (`tenant_users` table)                                                                                 |
| **Protection**       | User ID is a UUID FK (not the raw identity). Tenant isolation via composite unique constraint `[tenantId, userId]`. |
| **Retention**        | Until membership is deactivated or user/tenant is deleted.                                                          |
| **Access**           | Platform service only. All queries include `tenantId` filter.                                                       |
| **Deletion**         | `onDelete: Cascade` from both `Tenant` and `User`.                                                                  |
| **Schema reference** | `packages/db/prisma/schema.prisma:569-599`                                                                          |

### 3. OAuth Tokens (Third-Party Integrations)

| Field                | Details                                                                                                                                                        |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | Authentication credentials                                                                                                                                     |
| **Personal data**    | OAuth access token, refresh token, provider metadata (workspace name, external user ID, email)                                                                 |
| **Source**           | OAuth authorization flow (e.g., Notion, Google Drive)                                                                                                          |
| **Purpose**          | Enable AI agent to access user's third-party data sources on their behalf                                                                                      |
| **Legal basis**      | Consent (Art. 6(1)(a)) — user explicitly authorizes the OAuth connection                                                                                       |
| **Storage location** | Aurora MySQL (`oauth_connections` table)                                                                                                                       |
| **Protection**       | Access and refresh tokens: AES-256-GCM encrypted (`accessTokenEncrypted`, `refreshTokenEncrypted`). Provider metadata: JSON, may contain email/workspace info. |
| **Retention**        | Until user disconnects the integration or account is deleted.                                                                                                  |
| **Access**           | Application server only. Decryption requires `CODA_DB_IDENTITY_AES_KEY`.                                                                                       |
| **Deletion**         | Explicit disconnect via API. **Note:** `onDelete: Cascade` is **not set** on the `User` relation — requires manual deletion or schema fix.                     |
| **Schema reference** | `packages/db/prisma/schema.prisma:64-93`                                                                                                                       |

### 4. Conversation History

| Field                | Details                                                                                                                                                                                                                                                                                                                             |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | User-generated content                                                                                                                                                                                                                                                                                                              |
| **Personal data**    | Chat messages (user queries, AI responses), message metadata (timestamps, model used)                                                                                                                                                                                                                                               |
| **Source**           | User input via chat interface; AI model responses                                                                                                                                                                                                                                                                                   |
| **Purpose**          | Maintain conversation context for AI agent interactions; enable message history                                                                                                                                                                                                                                                     |
| **Legal basis**      | Contractual necessity (Art. 6(1)(b)) — core service functionality                                                                                                                                                                                                                                                                   |
| **Storage location** | Aurora MySQL (`chats`, `messages` tables). Redis (temporary conversation cache with TTL).                                                                                                                                                                                                                                           |
| **Protection**       | DB: standard Aurora encryption at rest (AES-256). Redis: TLS for remote connections. **Note:** conversation cache keys currently use raw `identityId`, not HMAC hash — this should be changed to use `hashIdentity()` for consistency with DB storage. Message content is not additionally encrypted beyond Aurora disk encryption. |
| **Retention**        | Soft deletion (`deleted_at` timestamp). No automated purge schedule. Redis cache TTL refreshed on access.                                                                                                                                                                                                                           |
| **Access**           | Scoped to owning user. Phase 2D adds `ResourceShare` for controlled sharing.                                                                                                                                                                                                                                                        |
| **Deletion**         | Soft delete sets `deleted_at`. Hard delete available via cascade from `User`.                                                                                                                                                                                                                                                       |
| **Schema reference** | `packages/db/prisma/schema.prisma:100+`                                                                                                                                                                                                                                                                                             |

### 5. Audit Logs

| Field                | Details                                                                                                                                                                                              |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | Security and compliance records                                                                                                                                                                      |
| **Personal data**    | User ID, IP address, user agent — all encrypted. Action, resource, outcome — not PII.                                                                                                                |
| **Source**           | Platform permission pipeline; API request middleware                                                                                                                                                 |
| **Purpose**          | Security auditing, compliance (SOC 2, SOX), forensic investigation, GDPR accountability                                                                                                              |
| **Legal basis**      | Legitimate interest (Art. 6(1)(f)) — security monitoring; Legal obligation (Art. 6(1)(c)) — SOX/SOC 2 requirements                                                                                   |
| **Storage location** | Aurora MySQL (`audit_logs` table). Redis (temporary buffer before DB flush).                                                                                                                         |
| **Protection**       | PII fields encrypted per-user via AES-256-GCM with HMAC-derived keys. Envelope encryption (DEK wrapped by AWS KMS). Redis buffer contains only encrypted PII — `encryptEntry()` runs before `LPUSH`. |
| **Retention**        | Immutable — no updates or deletes. `onDelete: Restrict` prevents cascade. Crypto-shredding renders PII unreadable on user erasure.                                                                   |
| **Access**           | Tenant-scoped queries only (`[tenantId, createdAt]` index). Super admins with `full` or `read_only` level.                                                                                           |
| **Deletion**         | Records are never deleted. PII is erased via crypto-shredding (deleting `AuditEncryptionKey`).                                                                                                       |
| **Schema reference** | `packages/db/prisma/schema.prisma:996-1034`                                                                                                                                                          |

### 6. Permission Change Logs

| Field                | Details                                                                |
| -------------------- | ---------------------------------------------------------------------- |
| **Data category**    | Security and compliance records                                        |
| **Personal data**    | Target user ID, changed-by user ID, change details (JSON)              |
| **Source**           | Platform service (role assignment, permission override, SoD changes)   |
| **Purpose**          | Compliance auditing (SOC 2, SOX); historical permission reconstruction |
| **Legal basis**      | Legitimate interest (Art. 6(1)(f)); Legal obligation (Art. 6(1)(c))    |
| **Storage location** | Aurora MySQL (`permission_change_logs` table)                          |
| **Protection**       | `onDelete: Restrict`. User IDs are UUIDs (not raw identities).         |
| **Retention**        | Immutable. No automated purge.                                         |
| **Access**           | Tenant-scoped.                                                         |
| **Deletion**         | Records are never deleted.                                             |
| **Schema reference** | `packages/db/prisma/schema.prisma:1040-1064`                           |

### 7. Session Data

| Field                | Details                                                                                                                                               |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | Technical identifiers                                                                                                                                 |
| **Personal data**    | Session token (SHA-256 hashed), IP address, user agent                                                                                                |
| **Source**           | Platform session creation                                                                                                                             |
| **Purpose**          | Session management; security monitoring (detect concurrent sessions, suspicious IPs)                                                                  |
| **Legal basis**      | Contractual necessity (Art. 6(1)(b)); Legitimate interest (Art. 6(1)(f))                                                                              |
| **Storage location** | Aurora MySQL (`sessions` table)                                                                                                                       |
| **Protection**       | Token: SHA-256 hash only (plaintext never stored). IP and user agent: stored as plaintext (not encrypted — Phase 2 should evaluate encrypting these). |
| **Retention**        | Sessions expire via `expiresAt`. Revoked sessions retained for audit purposes.                                                                        |
| **Access**           | Tenant-scoped.                                                                                                                                        |
| **Schema reference** | `packages/db/prisma/schema.prisma:881-909`                                                                                                            |

### 8. Super Admin Grants

| Field                | Details                                                                             |
| -------------------- | ----------------------------------------------------------------------------------- |
| **Data category**    | Access control records                                                              |
| **Personal data**    | User ID, granted-by user ID, revoked-by user ID                                     |
| **Source**           | Platform administration                                                             |
| **Purpose**          | Track cross-tenant privileged access for accountability                             |
| **Legal basis**      | Legitimate interest (Art. 6(1)(f))                                                  |
| **Storage location** | Aurora MySQL (`super_admins` table)                                                 |
| **Protection**       | User IDs are UUIDs. Grants include `revokedAt` for lifecycle tracking.              |
| **Retention**        | Retained indefinitely for audit trail (historical grants with `revokedAt` != null). |
| **Access**           | Platform service only. Not tenant-scoped (cross-tenant by design).                  |
| **Schema reference** | `packages/db/prisma/schema.prisma:942-962`                                          |

### 9. Data Sources

| Field                | Details                                                                                                                                                                                                                                  |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | User-owned query definitions                                                                                                                                                                                                             |
| **Personal data**    | Data source name, SQL/GraphQL query text, cached result data (may contain business-sensitive royalty data)                                                                                                                               |
| **Source**           | User creation via platform UI                                                                                                                                                                                                            |
| **Purpose**          | Store and re-execute named queries for the AI agent                                                                                                                                                                                      |
| **Legal basis**      | Contractual necessity (Art. 6(1)(b))                                                                                                                                                                                                     |
| **Storage location** | Aurora MySQL (`data_sources` table). S3 (`cachedResultKey` — cached query results).                                                                                                                                                      |
| **Protection**       | DB: Aurora encryption at rest. S3: server-side encryption (SSE-S3 or SSE-KMS — verify bucket policy). Cached results may contain business-sensitive data but are not additionally encrypted at the application layer.                    |
| **Retention**        | Until user deletes or account is deleted. Cached results have no automatic TTL in S3.                                                                                                                                                    |
| **Access**           | Scoped to owning user.                                                                                                                                                                                                                   |
| **Deletion**         | Cascade from `User` — **Note:** `DataSource` relation to `User` does not currently specify `onDelete: Cascade` in the Prisma schema; manual deletion or schema fix required. S3 objects must be deleted separately (not cascade-linked). |
| **Schema reference** | `packages/db/prisma/schema.prisma` (`DataSource` model)                                                                                                                                                                                  |

### 10. Redis Cached Data

| Field                | Details                                                                                                                                                                       |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data category**    | Transient operational data                                                                                                                                                    |
| **Personal data**    | Permission resolution results (keyed by `tenantId:userId`), conversation message cache (keyed by raw `identityId` — should be HMAC-hashed), rate limit counters (keyed by IP) |
| **Source**           | Application runtime                                                                                                                                                           |
| **Purpose**          | Performance optimization; rate limiting enforcement                                                                                                                           |
| **Legal basis**      | Legitimate interest (Art. 6(1)(f)) — operational necessity                                                                                                                    |
| **Storage location** | Redis (ElastiCache in production, localhost in dev)                                                                                                                           |
| **Protection**       | TLS for remote connections. Permission cache keys include tenant ID for isolation. Conversation cache keyed by HMAC hash (not raw identity).                                  |
| **Retention**        | TTL-based: permissions (30s–1h), rate limits (1 min window), conversations (configurable).                                                                                    |
| **Access**           | Application server only.                                                                                                                                                      |
| **Deletion**         | Automatic via TTL expiry. Tenant suspension triggers prefix-scan cache purge.                                                                                                 |

---

## Data Flows

```
User Browser
    |
    | HTTPS (TLS 1.2+)
    v
[ALB / HAProxy] ──── HSTS, CSP headers
    |
    v
[Express Server (apps/server)]
    |── Auth0 JWT validation
    |── Identity: raw ID → HMAC hash + AES encrypt → DB
    |── Conversations → Aurora (chat, messages)
    |── OAuth tokens → AES-256-GCM encrypt → Aurora
    |── Rate limit → Redis (IP-based counters)
    |── Conversation cache → Redis (HMAC-keyed)
    |
    |── ConnectRPC ──→ [Platform Service (apps/platform)]
    |                      |── Permission check → Redis cache / Aurora DB
    |                      |── Audit entry → encrypt PII → Redis buffer → Aurora
    |                      |── Permission changes → Aurora (append-only)
    |
    |── ConnectRPC ──→ [Search Service (apps/search)]
    |                      |── No PII stored. Index data only.
    |
    v
[Snowflake (read-only)]
    |── Session variables for RLS
    |── No PII written; reads reporting data
```

---

## Sub-Processor Register

| Sub-Processor | Data Processed                       | Purpose                                                                         | DPA Status           | Data Location                                                |
| ------------- | ------------------------------------ | ------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------ |
| **AWS**       | All data (Aurora, Redis, S3, KMS)    | Infrastructure hosting, encryption key management                               | AWS DPA (standard)   | us-east-1 (default), configurable per `Tenant.dataResidency` |
| **Auth0**     | JWT tokens, user profiles            | Authentication, identity management                                             | Auth0 DPA (standard) | Auth0 cloud (US)                                             |
| **Sentry**    | Error stack traces, request metadata | Error monitoring. `sendDefaultPii: false` — no PII sent by default.             | Sentry DPA           | Sentry cloud (US)                                            |
| **Datadog**   | APM traces, metrics, logs            | Performance monitoring, AppSec. No PII in traces (application-level redaction). | Datadog DPA          | Datadog cloud (US)                                           |
| **Snowflake** | Royalty reporting data (read-only)   | Data source for AI agent queries                                                | Snowflake DPA        | AWS us-east-1                                                |

---

## International Transfers

| Transfer          | Mechanism                               | Notes                                             |
| ----------------- | --------------------------------------- | ------------------------------------------------- |
| EU → US (AWS)     | AWS Standard Contractual Clauses (SCCs) | `Tenant.dataResidency` allows EU region selection |
| EU → US (Auth0)   | Auth0 SCCs                              | Authentication only, no content data              |
| EU → US (Sentry)  | Sentry SCCs                             | Error traces only, PII filtered                   |
| EU → US (Datadog) | Datadog SCCs                            | Metrics and traces only                           |

---

## Maintaining This Document

- **Review cadence:** Quarterly, or when new data categories are added.
- **Trigger for update:** New table with PII columns, new sub-processor, new data flow.
- **Owner:** Platform team lead + DPO (when appointed).
- **Phase 2 additions:** `ConsentRecord`, `PrivacyRequest`, `DataClassification`, `Invitation`, `ScimToken`, `Department`, `UserGroup` — each needs a processing activity entry.
