# Personal data

This page describes player data only. Workforce identity (staff and partner admins) is a separate domain and may use business email ([ADR 0012](../decisions/0012-never-receive-player-pii.md)).

## Policy

> **Status: Implemented** — evidence: [ADR 0012](../decisions/0012-never-receive-player-pii.md), [spec §2.2](../../spec/PLAYERPAL_AUTH_IDENTITY_SPEC.md)

1. **No direct identifiers.** PlayerPal-controlled persistence, tokens, logs, traces, events and analytics must never contain a player's email, name or telephone number ([ADR 0012](../decisions/0012-never-receive-player-pii.md)).
2. **Pseudonymous data is personal data.** PlayerPal treats pseudonymous player identifiers and gameplay data as personal data. The spec says they "may remain personal data under applicable privacy law even though they are not direct identifiers" and must be "protected, access-controlled, retained appropriately and included in privacy-impact analysis" (spec §2.2). This is a product policy, not a legal conclusion.

## What we never receive

> **Status: Implemented** (code and tests) / **Not yet verified live** — evidence: [oidc.service.ts](../../services/api/src/auth/oidc.service.ts), [oidc.claims.spec.ts](../../services/api/src/auth/oidc.claims.spec.ts)

| Data | How it is kept out |
|---|---|
| Email, name, phone from sign-in | Player OIDC requests scope `openid` only. Only issuer and subject are kept; email, name and phone claims are ignored. IdP raw profile data is stripped before return ([oidc.service.ts](../../services/api/src/auth/oidc.service.ts); test "keeps only issuer and subject when the token carries email name phone" in [oidc.claims.spec.ts](../../services/api/src/auth/oidc.claims.spec.ts)) |
| Columns for those fields | None of the entities in [db/entities](../../services/api/src/db/entities/) has an email, name, phone, address or birth-date column. The schema notes "Never store email, name, or phone" ([initial-schema.sql](../../services/api/src/db/migrations/initial-schema.sql)) |
| Direct identifiers in partner facts | `player_ref` must match a restricted reference pattern and be an alias PlayerPal issued to that partner ([ingest.schemas.ts](../../services/api/src/ingest/ingest.schemas.ts); test "never accepts direct PII-shaped refs" in [ingest.schemas.spec.ts](../../services/api/src/ingest/ingest.schemas.spec.ts)) |
| Email-based linking or recovery | Disabled. Email magic links and SMS OTP are not player sign-in methods ([ADR 0012](../decisions/0012-never-receive-player-pii.md), [ADR 0028](../decisions/0028-v1-recovery-without-operator-assist.md)) |
| Operator credentials or session cookies | Excluded by the spec (spec §2.2) |

Two caveats from the repo:

- The IAM may require a synthetic placeholder email on its side when creating a user. PlayerPal never persists it ([ADR 0012 amendment](../decisions/0012-never-receive-player-pii.md)).
- A live capture proving that social sign-in leaves no email, name or phone in PlayerPal cookies, database, logs or traces has not been done yet. Code filtering is in place; the live hop is not verified ([open-questions.md](../open-questions.md)).

## Identifiers

> **Status: Implemented** — evidence: [identity.md](../identity.md), [ADR 0013](../decisions/0013-typed-pairwise-aliases.md)

| Identifier | Who may see it | Notes |
|---|---|---|
| Internal UUIDv7 | PlayerPal only | Database key, never exposed |
| `pp_plr_...` | Player and PlayerPal | Never sent to any partner |
| `pp_plr_opr_...` | That operator | Random per operator ([ADR 0002](../decisions/0002-random-pairwise-external-ids.md)) |
| `pp_plr_prv_...` | That provider-operator integration | One per integration, so a provider cannot join a player across casinos ([ADR 0014](../decisions/0014-provider-operator-alias-scope.md)) |
| `pp_anl_...` analytics subject | PlayerPal only | Never on a player or partner API ([ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md)) |
| IAM subject | PlayerPal only | Opaque sign-in subject, not returned on `/v1/me` |

**Aliases** are random 128-bit values, not derived from the UUID or any personal data. They are minted only for audiences on a completed link, never on activity ingest ([ADR 0027](../decisions/0027-alias-provisioning-at-link.md)). A foreign alias gets the same response as an unknown one ([ADR 0014](../decisions/0014-provider-operator-alias-scope.md)). Affiliate and reward-partner prefixes are reserved and not minted ([ADR 0013](../decisions/0013-typed-pairwise-aliases.md)).

**Analytics subject** is minted at Account activation, random, and used to cluster retained activity without the UUID ([ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md); tests in [analytics-subject.spec.ts](../../services/api/src/identity/analytics-subject.spec.ts), including "writes the analytics subject onto processed round_settled and hides it from APIs").

**IAM subject storage.** The `authentication_identities` row holds ([authentication-identity.entity.ts](../../services/api/src/db/entities/authentication-identity.entity.ts), [identity.service.ts](../../services/api/src/identity/identity.service.ts), [common/ids.ts](../../services/api/src/common/ids.ts)):

- `subject_lookup_hmac`: HMAC-SHA256 of issuer and subject, keyed with a pepper (`SUBJECT_PEPPER`), used for lookup.
- `subject_ciphertext`: despite the name, this is also a keyed HMAC of the subject (one-way), not reversible encryption.
- `iam_subject`: the opaque subject in plain text, used for password sign-in reverse lookup. The docs call it "not PII" ([data-model.md](../data-model.md), [CONTEXT.md](../../CONTEXT.md)).

Discrepancy: spec §20.2 asks to "encrypt sensitive mappings at application level" and keep keys in a managed KMS/HSM. The code reads the pepper from configuration and stores the plain `iam_subject`; no application-level encryption or KMS use is visible in the code.

## Unlink and Delete Account

> **Status: Implemented** — evidence: [ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md), [ADR 0030](../decisions/0030-unlink-delete-on-playerpal-account.md), [identity-unlink.spec.ts](../../services/api/src/identity/identity-unlink.spec.ts), [identity-delete.spec.ts](../../services/api/src/identity/identity-delete.spec.ts)

Both verbs live only on the PlayerPal Account screen, need recent reauthentication and take an `Idempotency-Key` ([identity.md](../identity.md)).

| | Unlink (one audience) | Delete Account |
|---|---|---|
| Aliases | Revoked, never reassigned; relink mints a new alias | All revoked |
| Later activity | Does not earn; delayed activity that occurred before Unlink still qualifies | A former alias is treated as unknown |
| Points | Stay on the ledger | Identity graph erased |
| Sign-in data | Unchanged | Authentication identities, passkeys, sessions deleted; IAM user deleted first. If IAM delete fails, the request fails and the Account stays active |
| Analytics | No new partner-player-day rows; history stays | Partner-player-day rows and fact rows deleted; `player_id` / `player_alias` nulled on retained activity; UUID↔analytics-subject mapping destroyed |
| Retained | Historical analytics days | `round_ref`, amounts, `occurred_at`, integration and analytics subject on activity; redemption rows with `player_id` nulled ([identity.store.ts](../../services/api/src/db/repositories/identity.store.ts)) |
| Partner | Optional async notice; Unlink does not wait for it ([ADR 0032](../decisions/0032-unlink-partner-notice.md)) | PlayerPal does not command partner erasure |

v1 does not crypto-shred backups. A restored pre-delete backup can still join, and the docs say "do not market otherwise" ([ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md)).

## Who can see what

> **Status: Implemented** (API, scoping and floors) / **In progress (demo only)** (partner portal sign-in) — evidence: [services/cube/security.js](../../services/cube/security.js), [kpi.service.ts](../../services/api/src/analytics/kpi/kpi.service.ts), [comparison.math.ts](../../services/api/src/analytics/compare/comparison.math.ts)

- **Partner scoping.** Partners call the NestJS KPI gateway, never the semantic layer directly. Each query is limited to the cubes allowed for the party type, refuses `player_id` and ungrouped queries, and gets a `party_id` filter on every cube ([security.js](../../services/cube/security.js), [services/cube/README.md](../../services/cube/README.md)).
- **KPI account floor.** Partner KPI cells under the floor are suppressed. `KPI_ACCOUNT_FLOOR` (falls back to `STUDIO_ACCOUNT_FLOOR`) defaults to 50; staff tier uses a floor of 0 ([kpi.service.ts](../../services/api/src/analytics/kpi/kpi.service.ts)).
- **Studio analytics floor.** `STUDIO_ACCOUNT_FLOOR` defaults to 50. Per-alias lookup is n=1 by design and returns numbers only ([activity.service.ts](../../services/api/src/activity/activity.service.ts), [ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md)).
- **Benchmark floors** (`DEFAULT_ANONYMITY` in [comparison.math.ts](../../services/api/src/analytics/compare/comparison.math.ts)): at least 5 peer parties (`minPeers`), at least 50 distinct Accounts (`minAccounts`), and suppression when one party holds more than 50% (`dominanceP`) or the top two more than 75% (`dominanceP2`). Overrides: `BENCHMARK_MIN_PEERS`, `BENCHMARK_MIN_ACCOUNTS`, `BENCHMARK_DOMINANCE_P`, `BENCHMARK_DOMINANCE_P2`; lower values apply only with `BENCHMARK_ALLOW_LOW_THRESHOLDS=1` ([comparison.job.ts](../../services/api/src/analytics/compare/comparison.job.ts)).
- **Legacy studio header.** The shared machine key plus `x-party-id` is still accepted unless `STUDIO_LEGACY_PARTY_HEADER=false`, and must be turned off in production ([loyalty.md](../loyalty.md)).

## Retention and purpose limits

> **Status: Partly decided** — evidence: [ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md), [spec](../../spec/PLAYERPAL_AUTH_IDENTITY_SPEC.md)

There is no retention schedule document. What the repo does state:

- Delete Account and Unlink behaviour, as in the table above.
- Command `Idempotency-Key` values are kept for at least 24 hours (spec §37).
- The raw accepted envelope is stored for replay, and "retention and pseudonymisation policies" must apply to it as personal data. No period is given (spec §38.3).
- IP/network velocity limits use "short retention". No period is given. IP addresses and device fingerprints must never become durable player-linking keys (spec, abuse controls section).
- Purpose: separate identity mappings from general analytics access (spec §20.2); partners get numeric enrichment only, never another audience's identifier ([ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md)).

## Open questions

The repo does not decide these. Do not assume answers; record an ADR instead ([open-questions.md](../open-questions.md)).

- Retention periods for activity, facts, analytics serving rows, the raw envelope layer, ledger entries and audit data.
- Retention of anonymised gameplay facts after Delete Account (kept, but for how long is not stated).
- Backup retention, given backups are not crypto-shredded.
- Whether a social provider that emits email regardless of scope may be enabled (product, legal and security must decide; [ADR 0012](../decisions/0012-never-receive-player-pii.md)).
- Live capture of the social sign-in hop ([open-questions.md](../open-questions.md)).
- Audience scope for affiliate and reward-partner aliases ([ADR 0013](../decisions/0013-typed-pairwise-aliases.md)).
- Joint-controller contracts remain "a product-permission assumption" ([ADR 0026](../decisions/0026-analytics-subject-and-provider-cohort.md)).

Related: [Product overview](../product/overview.mdx), [Privacy (public)](../trust/privacy.mdx), [identity.md](../identity.md), [authorization.md](../authorization.md).
