docs(compliance): add docs/compliance reference examples and README
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>
This commit is contained in:
229
docs/compliance/README.md
Normal file
229
docs/compliance/README.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# 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`.
|
||||
79
docs/compliance/data-map.example.yml
Normal file
79
docs/compliance/data-map.example.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
# docs/compliance/data-map.example.yml — annotated reference for the data-map schema
|
||||
#
|
||||
# This file is NOT generated. It shows every possible field that can appear in
|
||||
# compliance/data-map.yml and explains what each field means.
|
||||
#
|
||||
# Generated artifact : compliance/data-map.yml
|
||||
# Generator : pnpm compliance:data-map
|
||||
# Source of truth : packages/*/src/integrations/cms/collections/*.ts
|
||||
# (custom.pii field tags + auth: true defaults)
|
||||
# PII types : packages/core-shared/src/payload/pii-types.ts
|
||||
|
||||
collections:
|
||||
# ── Non-auth collection with manually tagged PII fields ───────────────────────
|
||||
profiles:
|
||||
auth: false # true only for Payload auth-enabled collections (auth: true in collection config)
|
||||
slug: profiles # matches the Payload collection slug; used as the map key
|
||||
piiFields: # empty array ([]) when no PII fields are declared on the collection
|
||||
# ── Minimal field tag (no retention override) ─────────────────────────────
|
||||
- category:
|
||||
identification-name # PiiCategory — see packages/core-shared/src/payload/pii-types.ts
|
||||
# e.g. contact-email | identification-username | network-ip | …
|
||||
exportable: true # GDPR Art. 15 — include in data-export responses
|
||||
field: fullName # The Payload field name as declared in the collection
|
||||
purpose: # DataProcessingPurpose[] — why we collect and process this value
|
||||
- service-delivery # e.g. account-authentication | transactional-notifications |
|
||||
# marketing-communications | analytics-aggregation |
|
||||
# legal-compliance | service-delivery
|
||||
restrictable: true # User can request restriction of processing under GDPR Art. 18
|
||||
source: field-tag # Origin: "field-tag" — custom.pii tag on the collection field
|
||||
|
||||
# ── Field tag with a per-field retention override ─────────────────────────
|
||||
# Use when a field needs a stricter retention window than the collection default.
|
||||
- category: network-ip
|
||||
exportable: false
|
||||
field: lastIpAddress
|
||||
purpose:
|
||||
- legal-compliance
|
||||
restrictable: false
|
||||
retention: # Optional. Per-field retention window (overrides collection schedule).
|
||||
action: hard-delete # RetentionAction: "hard-delete" | "pseudonymize"
|
||||
duration: P6M # ISO 8601 duration (P6M = 6 months, P1Y = 1 year, P90D = 90 days)
|
||||
trigger: from-last-access # RetentionTrigger: "from-creation" | "from-last-access" | "after-deletion"
|
||||
source: field-tag
|
||||
|
||||
# ── Auth-enabled collection: defaults + overrides ─────────────────────────────
|
||||
# When a collection sets auth: true, PAYLOAD_AUTH_PII_DEFAULTS are applied
|
||||
# automatically. Each default can be overridden via custom.authPii in the
|
||||
# collection config without adding custom.pii to every field individually.
|
||||
members:
|
||||
auth: true # Activates PAYLOAD_AUTH_PII_DEFAULTS (email injected, password/salt/hash excluded)
|
||||
slug: members
|
||||
piiFields:
|
||||
# Injected automatically because auth: true — no field tag needed on the collection
|
||||
- category: contact-email
|
||||
exportable: true
|
||||
field: email
|
||||
purpose:
|
||||
- account-authentication
|
||||
- transactional-notifications
|
||||
restrictable: true
|
||||
source: auth-default # From PAYLOAD_AUTH_PII_DEFAULTS; not declared in collection fields
|
||||
|
||||
# The collection supplied custom.authPii.phone to extend or change the auth defaults
|
||||
- category: contact-phone
|
||||
exportable: true
|
||||
field: phone
|
||||
purpose:
|
||||
- transactional-notifications
|
||||
restrictable: true
|
||||
source: auth-override # Collection explicitly overrode or extended the auth default
|
||||
|
||||
# Regular custom.pii tag on a field inside an auth collection
|
||||
- category: identification-username
|
||||
exportable: true
|
||||
field: username
|
||||
purpose:
|
||||
- service-delivery
|
||||
restrictable: true
|
||||
source: field-tag
|
||||
59
docs/compliance/retention-policy.example.yml
Normal file
59
docs/compliance/retention-policy.example.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
# docs/compliance/retention-policy.example.yml — annotated reference for the retention-policy schema
|
||||
#
|
||||
# This file is NOT generated. It shows every possible field that can appear in
|
||||
# compliance/retention-policy.yml and explains what each field means.
|
||||
#
|
||||
# Generated artifact : compliance/retention-policy.yml
|
||||
# Generator : pnpm compliance:retention-policy
|
||||
# Source of truth : packages/*/src/integrations/cms/collections/*.ts
|
||||
# (custom.retention block inside each collection config)
|
||||
# PII types : packages/core-shared/src/payload/pii-types.ts
|
||||
|
||||
collections:
|
||||
# ── Collection with all optional retention fields populated ───────────────────
|
||||
# This represents a user-identity collection with the strictest schedule.
|
||||
profiles:
|
||||
slug: profiles # matches the Payload collection slug; used as the map key
|
||||
purgeSchedule:
|
||||
daily # Required. Cadence for the background purge job.
|
||||
# Allowed values: "daily" | "weekly" | "monthly"
|
||||
# Set via custom.retention.purgeSchedule in the collection config.
|
||||
|
||||
# Optional. How long to keep an active record before it enters the delete flow.
|
||||
# Omit if records should live indefinitely until a user-deletion request is received.
|
||||
activeRetention:
|
||||
duration: P2Y # ISO 8601 duration (P2Y = 2 years, P1Y = 1 year, P6M = 6 months, P90D = 90 days)
|
||||
trigger:
|
||||
from-last-access # RetentionTrigger: when the clock starts
|
||||
# "from-creation" — counted from record creation date
|
||||
# "from-last-access" — resets on every authenticated session
|
||||
|
||||
# Optional. Long-term cold-storage window before final purge.
|
||||
# Use when regulatory obligations require records to survive deletion requests for a fixed term
|
||||
# (e.g. financial records under EU accounting law, seven-year AML retention).
|
||||
coldArchive:
|
||||
duration: P7Y
|
||||
trigger: from-creation # Usually from-creation for regulatory fixed-term obligations
|
||||
|
||||
# Required. What happens after a user submits a deletion request (DSR erasure / account closure).
|
||||
postDeletion:
|
||||
action:
|
||||
pseudonymize # RetentionAction: "hard-delete" | "pseudonymize"
|
||||
# hard-delete — row is permanently removed at the end of the grace period
|
||||
# pseudonymize — PII columns are replaced with opaque tokens; row is kept
|
||||
# (use when the record must survive for aggregate analytics)
|
||||
duration: P30D # Grace period before the action executes (ISO 8601 duration)
|
||||
trigger: after-deletion # Fixed value — clock starts when the deletion request is accepted
|
||||
|
||||
# ── Collection with only the required minimum fields ─────────────────────────
|
||||
# Most content collections (articles, pages, media) use this minimal form.
|
||||
articles:
|
||||
slug: articles
|
||||
purgeSchedule: monthly # Low-sensitivity content; monthly sweep is sufficient
|
||||
|
||||
# postDeletion is required even for non-PII collections.
|
||||
# For content without PII, hard-delete with a short grace period is the default.
|
||||
postDeletion:
|
||||
action: hard-delete
|
||||
duration: P90D
|
||||
trigger: after-deletion
|
||||
68
docs/compliance/sub-processors.example.yml
Normal file
68
docs/compliance/sub-processors.example.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
# docs/compliance/sub-processors.example.yml — annotated reference for the sub-processors schema
|
||||
#
|
||||
# This file is NOT generated. It shows both kinds of entries that appear in
|
||||
# compliance/sub-processors.yml and explains what each field means.
|
||||
#
|
||||
# Generated artifact : compliance/sub-processors.yml
|
||||
# Generator : pnpm compliance:sub-processors
|
||||
# Sources : (1) docs/library-decisions/*.md — frontmatter where is-sub-processor: true
|
||||
# (2) compliance/sub-processors.manual.yml — hand-authored non-SDK vendors
|
||||
#
|
||||
# The two entry kinds are described below. Fields are rendered in a fixed order:
|
||||
# package first, then remaining fields alphabetically.
|
||||
|
||||
sub-processors:
|
||||
# ── Kind 1: library-trace entry ───────────────────────────────────────────────
|
||||
# Sourced automatically from a library-decision file in docs/library-decisions/.
|
||||
# To register a library as a sub-processor, add these fields to its frontmatter:
|
||||
#
|
||||
# is-sub-processor: true
|
||||
# data-sent: "user email and display name for transactional notifications"
|
||||
# region: EU
|
||||
# dpa-signed: true
|
||||
# sccs-required: false
|
||||
# contact: https://acme-sdk.example/privacy/dpa
|
||||
#
|
||||
# The generator reads package, version, and decision from the existing trace
|
||||
# frontmatter and merges the sub-processor fields automatically.
|
||||
- package: "@acme/notifications-sdk" # npm package name (natural sort key; must match package: in trace)
|
||||
contact: https://acme-sdk.example/privacy/dpa # DPA / privacy contact URL
|
||||
data-sent:
|
||||
"user email address and display name for transactional notification delivery"
|
||||
# Plain-language description of what data is transmitted
|
||||
decision: approved # Carry-through from library trace (approved | rejected | …)
|
||||
dpa-signed: true # Data Processing Agreement in place with this vendor
|
||||
region: EU # Primary data-residency region for this processor
|
||||
sccs-required:
|
||||
false # Standard Contractual Clauses required for data transfer?
|
||||
# Required when region is outside the EEA and no adequacy decision
|
||||
source: library-trace # Fixed value for entries sourced from a library decision file
|
||||
version: "^2.3.0" # npm version specifier from the library trace
|
||||
|
||||
# ── Kind 2: manual entry ──────────────────────────────────────────────────────
|
||||
# For sub-processors that are NOT npm packages (REST APIs, SaaS integrations,
|
||||
# infrastructure providers, etc.), create compliance/sub-processors.manual.yml
|
||||
# and add entries there. The generator merges this file at emit time and injects
|
||||
# source: "manual" automatically.
|
||||
#
|
||||
# Format for compliance/sub-processors.manual.yml (simple YAML list, no header):
|
||||
#
|
||||
# - package: acme-email-service
|
||||
# version: REST API v3
|
||||
# data-sent: "user email address and message content"
|
||||
# region: EU
|
||||
# dpa-signed: true
|
||||
# sccs-required: false
|
||||
# contact: https://acme-email.example/legal/dpa
|
||||
# decision: approved
|
||||
#
|
||||
# The "package" field is used as the sort key; use a slug-style identifier.
|
||||
- package: acme-email-service # Slug identifier for non-npm vendors (no @ prefix)
|
||||
contact: https://acme-email.example/legal/dpa
|
||||
data-sent: "user email address and message body for transactional email delivery"
|
||||
decision: approved
|
||||
dpa-signed: true
|
||||
region: EU
|
||||
sccs-required: false
|
||||
source: manual # Fixed value for entries sourced from sub-processors.manual.yml
|
||||
version: "REST API v3" # Non-npm version descriptor; use the API version or contract date
|
||||
Reference in New Issue
Block a user