# docs/compliance — reference examples for the compliance module This folder contains **annotated example files** that document the schema used by the compliance generators. It is **not** the live compliance artifact directory. | Location | Contents | Edit manually? | | -------------------------------- | ------------------------------- | ----------------------- | | `docs/compliance/` (this folder) | Schema examples and this README | Yes — static reference | | `compliance/` (repo root) | Live generated artifacts | No — run the generators | --- ## What each file in `compliance/` contains ### `compliance/data-map.yml` A field-level PII inventory derived from every Payload collection in the workspace. For each collection the generator records: - **auth** — whether the collection has Payload authentication enabled - **piiFields** — every field carrying a `custom.pii` tag, plus Payload auth defaults (`email`, etc.) for auth collections Each PII field entry captures the **category** (e.g. `contact-email`, `identification-username`), one or more **purposes** (e.g. `account-authentication`, `service-delivery`), whether the field is **exportable** (GDPR Art. 15) and **restrictable** (GDPR Art. 18), the **source** (`field-tag`, `auth-default`, or `auth-override`), and an optional per-field **retention** override. See `docs/compliance/data-map.example.yml` for every field with annotations. ### `compliance/retention-policy.yml` A collection-level retention schedule derived from `custom.retention` blocks in Payload collection configs. For each collection the generator records: - **purgeSchedule** — cadence for the background purge job (`daily` | `weekly` | `monthly`); **required** on every collection - **activeRetention** _(optional)_ — how long to keep a live record before triggering deletion - **coldArchive** _(optional)_ — long-term archive window for regulatory fixed-term obligations - **postDeletion** — what happens after a DSR erasure or account-closure request (`hard-delete` or `pseudonymize` with an ISO 8601 grace period) See `docs/compliance/retention-policy.example.yml` for every field with annotations. ### `compliance/sub-processors.yml` An inventory of every third-party processor that receives personal data from this application. Entries come from two sources and are merged at emit time: 1. **Library decision traces** (`docs/library-decisions/*.md`) — npm packages where `is-sub-processor: true` in the frontmatter. 2. **Manual entries** (`compliance/sub-processors.manual.yml`) — non-npm vendors (REST APIs, SaaS integrations, infrastructure providers). Each entry records the **package** name (sort key), **data-sent** description, **region**, **dpa-signed** and **sccs-required** booleans, a **contact** URL, the **decision** status, and a **source** discriminator (`library-trace` or `manual`). See `docs/compliance/sub-processors.example.yml` for both entry kinds with annotations. --- ## How the files are generated All three artifacts are regenerated together: ```bash pnpm compliance:emit-all ``` Or individually: ```bash pnpm compliance:data-map # writes compliance/data-map.yml pnpm compliance:retention-policy # writes compliance/retention-policy.yml pnpm compliance:sub-processors # writes compliance/sub-processors.yml ``` Each script also supports two diagnostic modes: ```bash pnpm compliance:data-map -- --print # write YAML to stdout (no file written) pnpm compliance:data-map -- --check # diff vs committed file; exit 1 on mismatch ``` `--check` mode is used by the pre-commit hook and CI drift gate to detect uncommitted changes to collection configs that would cause `compliance/*.yml` to drift from the source of truth. --- ## Keeping `compliance/*.yml` up to date Regenerate and commit the artifacts whenever you: - Add or modify a Payload collection in any feature package - Change `custom.pii` tags on collection fields - Change `custom.retention` blocks on collection configs - Add a library decision trace with `is-sub-processor: true` - Add or update entries in `compliance/sub-processors.manual.yml` The pre-commit hook and CI gate run `emit-all --check` and will reject the commit or PR if the committed YAML is stale. --- ## Annotating PII fields in a collection Add `custom.pii` to any field in a Payload collection config: ```ts { name: "phone", type: "text", custom: { pii: { category: "contact-phone", // PiiCategory purpose: ["transactional-notifications"], // DataProcessingPurpose[] exportable: true, restrictable: true, // Optional per-field retention override: retention: { duration: "P1Y", // ISO 8601 trigger: "from-last-access", // "from-creation" | "from-last-access" | "after-deletion" action: "hard-delete", // "hard-delete" | "pseudonymize" }, }, }, }, ``` For auth collections, `email` is automatically classified via `PAYLOAD_AUTH_PII_DEFAULTS`. Override per-collection via `custom.authPii`: ```ts { slug: "members", auth: true, custom: { authPii: { // Extend or replace the email default for this collection only: email: { category: "contact-email", purpose: ["account-authentication", "marketing-communications"], exportable: true, restrictable: true, }, }, }, } ``` See `packages/core-shared/src/payload/pii-types.ts` for the full list of allowed `PiiCategory` and `DataProcessingPurpose` values. --- ## Annotating retention policy in a collection Add `custom.retention` to the collection config. `purgeSchedule` is **required** on every collection; the other fields are optional: ```ts { slug: "profiles", custom: { retention: { purgeSchedule: "daily", // Required: "daily" | "weekly" | "monthly" activeRetention: { // Optional: expire live records duration: "P2Y", trigger: "from-last-access", }, coldArchive: { // Optional: regulatory fixed-term archive duration: "P7Y", trigger: "from-creation", }, postDeletion: { // Optional: override the default post-deletion behaviour action: "pseudonymize", // "hard-delete" | "pseudonymize" duration: "P30D", trigger: "after-deletion", }, }, }, } ``` --- ## Registering a sub-processor ### From an npm library (preferred) Add sub-processor fields to the library's decision trace in `docs/library-decisions/`: ```markdown --- package: "@acme/notifications-sdk" version: "^2.3.0" decision: approved is-sub-processor: true data-sent: "user email address and display name for transactional notification delivery" region: EU dpa-signed: true sccs-required: false contact: https://acme-sdk.example/privacy/dpa --- ``` Run `pnpm compliance:sub-processors` to regenerate. ### Non-npm vendors (REST APIs, SaaS, infrastructure) Create `compliance/sub-processors.manual.yml` if it doesn't already exist and add an entry: ```yaml - package: acme-email-service # slug-style identifier (no @ prefix) version: "REST API v3" data-sent: "user email address and message body for transactional email delivery" region: EU dpa-signed: true sccs-required: false contact: https://acme-email.example/legal/dpa decision: approved ``` `compliance/sub-processors.manual.yml` is a hand-authored file committed to the repository alongside the generated `compliance/sub-processors.yml`. The generator merges manual entries at emit time and injects `source: "manual"` automatically. Do **not** add `source:` manually — it will be overwritten. Run `pnpm compliance:sub-processors` after any change to regenerate `compliance/sub-processors.yml`. --- ## Policy templates The `docs/compliance/templates/` directory contains fill-in-the-blank runbooks for the operational policies required before a GDPR-covered launch. These are **template originals** — do not edit them in place. ### Copy-to-`compliance/` workflow 1. Copy the desired template from `docs/compliance/templates/` to `compliance/` (repo root): ```bash cp docs/compliance/templates/dsr-procedure.template.md compliance/dsr-procedure.md ``` 2. Open the copied file and replace every `[FILL IN:]` marker with the project-specific value. 3. Commit the filled file to the repo as a live compliance artifact (same cadence as `compliance/*.yml`). ### `[FILL IN:]` convention Every placeholder that requires a project-specific value is marked with the literal string `[FILL IN:]` followed by a short description of what is expected. This makes placeholders machine-detectable and easy to audit. To verify that all placeholders in your live artifacts have been resolved, run: ```bash grep -rn '\[FILL IN:' compliance/ ``` A zero-line result means all templates have been fully filled in. Any output identifies the file and line that still needs attention. --- ## `sccs-required` guidance Set `sccs-required: true` when: - The vendor's primary data-residency region is outside the EEA **and** - There is no EU adequacy decision covering that country (e.g. US before Privacy Shield replacement, India, most of Asia-Pacific) Set `sccs-required: false` when: - The vendor is EU/EEA-resident, **or** - The vendor's country has a current EU adequacy decision, **or** - The data transfer is covered by Binding Corporate Rules When `sccs-required: true`, ensure SCCs are incorporated in the DPA before marking `dpa-signed: true`.