Adds docs/compliance/ as the canonical onboarding reference for the compliance module, covering every field in each generated YAML artifact with inline annotations and explaining the docs/compliance/ (examples) vs compliance/ (live artifacts) split. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
230 lines
8.3 KiB
Markdown
230 lines
8.3 KiB
Markdown
# 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`.
|
|
|
|
---
|
|
|
|
## `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`.
|