diff --git a/docs/decisions/adr-025-eu-compliance-baseline.md b/docs/decisions/adr-025-eu-compliance-baseline.md new file mode 100644 index 0000000..69558c0 --- /dev/null +++ b/docs/decisions/adr-025-eu-compliance-baseline.md @@ -0,0 +1,345 @@ +## ADR-025 — EU compliance baseline (DPA/GDPR template scope) + +**Status:** Accepted +**Date:** 2026-05-18 +**Related:** ADR-006 (vertical feature packages), ADR-017 (OTel + observability PII boundary), ADR-018 (audit + compliance), ADR-022 (library evaluation policy — extended here for sub-processors), ADR-023 (CI security + supply chain — SBOM amended here), ADR-024 (product analytics channel) +**Companion PRDs** (one per epic, sequenced): + +- `docs/work/prds/compliance-manifests-pii-retention-subprocessors.prd.md` (Epic A) +- `docs/work/prds/dsr-consent-and-cookie-banner.prd.md` (Epic B) +- `docs/work/prds/security-headers-rate-limit-sbom.prd.md` (Epic C) +- `docs/work/prds/compliance-docs-scaffolds.prd.md` (Epic D) + +## Context + +A DPA/GDPR compliance playbook (22 sections, stack-agnostic) was reviewed against the template's current state on **2026-05-18**. The audit produced a clean three-way split: + +1. **Already covered** by prior ADRs: + - PII boundary on observability (ADR-017 §7 — `sendDefaultPii: false` CI gate, server-side scrubbing, replay masking) + - Audit logging baseline (ADR-018 — `@repo/core-audit`, DPA-aligned schema, `eraseSubject` pseudonymization) + - EU library residency (ADR-022 — hard filter in `/evaluate-library`) + - Supply-chain + CI security (ADR-023 — Renovate SHA pinning, Socket.dev, audit signatures, CodeQL, gitleaks, trace revalidation) + - Analytics PII deferral (ADR-024 — explicit consumer-policy boundary) + +2. **Template-shaped gap** — 10 items the playbook flagged that the template can codify _before_ any consumer adopts it. These are conformance-pattern shaped (manifest fields, brands, ESLint rules, generators) or scaffolding (interfaces, default middleware, fill-in docs). + +3. **Product-shaped (deferred)** — 3 items that need product shape before being meaningful. Each has a documented trigger condition for revisit. + +The motivating pressure: a consumer adopting this template today gets ~50% of the playbook's surface for free. The 10-item template-shaped gap is what this ADR plans (raising coverage to ~80%); the remaining ~20% product/process/legal scope stays consumer-owned and is documented as such. + +Audit + DSR is the canonical confusion to flag upfront: **audit _records_ personal-data access (immutable journal); DSR _acts_ on the underlying data in response to user requests (mutator/exporter).** They are sibling concerns, not duplicates. + +## Decision + +Ship the 10 template-shaped items across **four epics**, sequenced A → B → D with C interleaved opportunistically. Defer 3 items explicitly. Add 3 new manifest fields, 2 new optional cores, 3 new ESLint rules. + +### The 4 epics + +#### Epic A — Declarative compliance manifests + +**Items:** PII inventory + data retention + sub-processor inventory. + +Three declarative artifacts, each driven by a different surface and a generator that emits the audit-evidence YAML: + +| Artifact | Declaration site | Generator output | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| PII inventory | `custom.pii: { category, purpose, retention, exportable, restrictable }` per Payload **field** | `compliance/data-map.yml` | +| Retention policy | `custom.retention: { activeRetention, postDeletion, purgeSchedule, hardDeleteAfter }` per Payload **collection** | `compliance/retention-policy.yml` | +| Sub-processor inventory | Extended ADR-022 library traces — frontmatter fields `is-sub-processor`, `processes-pii`, `data-sent`, `region`, `dpa-signed`, `sccs-required`, `contact` | `compliance/sub-processors.yml` | + +**Key design decisions:** + +- **PII at the field level, not the manifest level.** PII is a storage question ("what personal data does this system hold?"), not an action question. Existing manifest fields (`audits`, `publishes`, `analyticsEvents`) are all event-shaped — emissions from use cases. PII fields don't fit that shape. Tagging at the field level puts the metadata where DSR consumes it at runtime. + +- **Retention at the collection level, not per use case.** Same rationale — retention is a storage property. Per-field PII retention overrides apply where the PII tag specifies a stricter retention than the collection default (more-specific wins). Background purge job in `core-shared/jobs` reads collection config at boot. + +- **Sub-processors via ADR-022 traces, not standalone file.** Every direct-dep library trace already records EU residency, license, CVE acceptance. Extending it with the sub-processor fields (DPA signed date, SCCs, contact, PII processed, data sent, region) unifies two related obligations in one record. Pure-HTTP sub-processors (REST calls without an SDK) get hand-authored entries with no backing trace as an exception, CI-flagged. + +The three generators run via `pnpm compliance:emit-all`. CI gate verifies generator output matches the source declarations (drift detection). + +**Background purge job:** `core-shared/jobs/retention-purge.job.ts`. Reads `custom.retention` from each collection at boot; schedules per-collection purge cadence; emits an `IAuditLog.record({ action: "DELETE", reason: "retention-policy" })` audit entry per row purged. + +#### Epic B — DSR + consent + cookie banner + +**Items:** DSR scaffold + consent abstraction + cookie consent UI. + +Builds the user-rights surface end-to-end: + +**`@repo/core-dsr`** — new optional core. Four interfaces: + +```ts +interface IDataExport { + exportSubjectData( + subjectId: string, + format: "json" | "json-ld", + ): Promise; +} +interface IDataDelete { + deleteSubjectData( + subjectId: string, + mode: "soft" | "cascade-hard", + ): Promise; +} +interface IDataRectify { + updateSubjectField( + subjectId: string, + collection: string, + field: string, + value: unknown, + ): Promise; +} +interface IProcessingRestriction { + setRestriction(subjectId: string, granted: boolean): Promise; + isRestricted(subjectId: string): Promise; +} +``` + +DSR ops walk Payload collections at runtime, using the field-level `custom.pii` tags from Epic A. `IDataExport` walks fields tagged `exportable: true`; `IDataDelete` walks all PII fields and cascades; `IProcessingRestriction` writes a flag on the user record that every read path checks. + +**Scope cuts on DSR:** + +- Art. 20 (portability) folded into Art. 15 (access) — same `IDataExport` with format option +- Art. 21 (objection) → consent epic via `IConsent.withdraw` +- Art. 22 (automated decision-making) → deferred (no ML in template; future ADR when a consumer adds automated decisions) + +DSR ops are themselves PII access events — every `IDataExport`/`IDataDelete`/`IDataRectify` call writes an audit entry. After `IDataDelete`, `core-audit.IAuditLog.eraseSubject(actorId, "pseudonymize")` scrubs the audit trail (preserves the events, removes the identifier). + +**`@repo/core-consent`** — new optional core. Sibling channel parallel to audit/analytics: + +```ts +interface IConsent { + isGranted(subjectId: string, category: ConsentCategory): Promise; + grant( + subjectId: string, + categories: ConsentCategory[], + record: ConsentRecord, + ): Promise; + withdraw(subjectId: string, categories: ConsentCategory[]): Promise; + getCategories(subjectId: string): Promise; +} +``` + +`ConsentCategory` is a consumer-typed string-literal-union (default: `"essential" | "functional" | "analytics" | "marketing"`, extensible). Conformance treatment: + +- Brand: `ConsentChecked` attached by `withConsent` wrapper at bind time +- Manifest field: `requiresConsent: ["analytics"]` per use case +- ESLint rule: `no-undeclared-consent-check` cross-checks `consent.requires("X")` literal calls +- Boot assertion: `assertFeatureConformance` requires `ConsentChecked` brand when `requiresConsent.length > 0` + +Consent grant/withdraw events are themselves audited (`auditLog.record({ action: "CONSENT_GRANT", category: "marketing" })`). + +**`` in `@repo/core-ui`** — atomic component. Default visual treatment baked for EU prominence requirements (Reject + Accept side-by-side, same size, equal prominence per EU regulator guidance). Granular (essential/functional/analytics/marketing). Consumer wires `IConsent` via React context. Storybook story doubles as human reading-room for compliant UX. + +**Endpoint scaffolds:** `/api/gdpr/{export,delete,rectify,restrict}` — consumer-wireable routes. Live in `apps/web-next/app/api/gdpr/` and `apps/web-tanstack/src/routes/api/gdpr/`. + +#### Epic C — Security hardening + +**Items:** Security headers middleware + rate-limit primitive + SBOM in CI. + +Small individual scope; grouped for dispatch efficiency. Each item is independent. + +- **Security headers middleware** — default Next.js + TanStack middleware shipping HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. CSP customizable per consumer (separate config; default is restrictive). Lives in `core-shared/security` plus per-framework re-export. + +- **Rate-limit primitive** — fourth conformance channel after audit/analytics/consent. `IRateLimit` interface in `core-shared/rate-limit`: + + ```ts + interface IRateLimit { + consume( + key: string, + weight?: number, + ): Promise<{ allowed: boolean; remaining: number; resetAt: Date }>; + reset(key: string): Promise; + } + ``` + + - Brand: `RateLimited` attached by `withRateLimit` wrapper + - Manifest field: `rateLimit: { window: "1m", budget: 60 }` per use case (defaults; runtime overrides via `ctx.rateLimit` config) + - ESLint rule: `no-undeclared-rate-limit` warns when a use case in an auth/write/export category lacks a `rateLimit` declaration + - Boot assertion: `assertFeatureConformance` requires `RateLimited` brand when `rateLimit` is set + - Consumer wires Redis/Upstash impl; `NoopRateLimit` always-allows for tests + dev + + Rate-limit budgets at the manifest level are _defaults_ — overridable at runtime via `ctx.rateLimit` config (mirrors how analytics backend is consumer-wired). Manifest declaration is for the binding gate; runtime values are deployment-environment-specific. + +- **SBOM in CI** — `cyclonedx-npm` step in `ci.yml`, artifact uploaded per release. Amendment to ADR-023. + +#### Epic D — Compliance docs scaffolds + +**Items:** Fill-in templates for runbooks, policies, and the pre-launch checklist. + +Pure docs work. Lands last so it references the manifest fields, interfaces, and middleware shipped by A/B/C. + +**Template-shipped (under `docs/compliance/`):** + +- `data-map.example.yml` — generator-output reference +- `retention-policy.example.yml` — generator-output reference +- `sub-processors.example.yml` — generator-output reference +- `incident-runbook.template.md` (fill-in) +- `dsr-procedure.template.md` +- `backup-policy.template.md` +- `password-policy.template.md` +- `device-policy.template.md` +- `onboarding.template.md` +- `offboarding.template.md` +- `README.md` (explains the `docs/compliance/` vs `compliance/` split) + +**Consumer-created (under `compliance/` at repo root):** + +- `data-map.yml` (generator output) +- `retention-policy.yml` (generator output) +- `sub-processors.yml` (generator output from extended ADR-022 traces) +- `*.md` (filled-in copies of templates) + +**Plus:** `docs/guides/pre-launch-compliance-checklist.md` — playbook §19 verbatim with template-specific wiring noted. + +### Sequencing + +**Order: A → B → D, with C interleaved opportunistically.** + +Hard dependencies: + +- B's `IDataExport`/`IDataDelete` walk Epic A's PII tags at runtime → A must finish before B story 1 +- D's `data-map.example.yml` documents Epic A's PII schema → A's design must be settled before D +- D's `dsr-procedure.template.md` references Epic B's endpoints → B should be design-settled before D's PRD is decomposed + +C is dependency-free; sandcastle picks C stories during gaps in A/B/D. + +### Deferrals (explicit, with revisit triggers) + +| Deferred | Why deferred | Trigger to revisit | +| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| **RBAC primitive** (roles + permissions + tenant scoping) | Needs product-side decisions: which roles exist, multi-tenant or not, permission granularity | First downstream consumer ships with a stable role model | +| **MFA + password policy + lockout** (`auth` feature extension) | Needs identity-infrastructure choices (TOTP/WebAuthn), threat-model-specific policy values, OTP delivery vendor (ADR-022 territory) | First downstream consumer establishes auth threat model | +| **Breach detection patterns** (failed-login burst, bulk-access anomaly, off-hours admin) | Needs real auth flows, analytics backend, on-call infrastructure, product-specific anomaly definitions | First downstream consumer has live traffic + observability backend | +| **GDPR Art. 22** (automated decision-making) — sub-deferral within Epic B | Template has no ML/automated decisions | First downstream consumer adds automated decisions | + +Each deferral has a documented trigger so the decision-when can be answered by the consumer, not the template authors. + +### Consumer-scope items (explicitly out of template) + +These appear in the playbook but are NOT template-shaped: + +- **Infrastructure (§1, §12)** — EU region pinning of compute/storage/backups, TLS at deploy edge, encryption-at-rest config, VPN/bastion network boundaries, backup strategy + restore testing +- **Legal (§17)** — DPA, Privacy Policy, ToS, SCCs for non-EU sub-processors, DPIA artifacts, RoPA documents +- **Organizational (§14, §15)** — MDM enrollment, HR onboarding/offboarding scripts (the _script_ is template; the _policy_ is consumer), NDAs, security training, background checks, quarterly access reviews, pentest scheduling + +Epic D ships fill-in templates for some documentation artifacts above; the values stay consumer-filled. + +### Manifest schema impact + +Per-use-case fields grow from 5 to 7: + +- Existing: `mutates`, `audits`, `publishes`, `consumes`, `analyticsEvents` +- Added by ADR-025: `requiresConsent`, `rateLimit` + +Per-Payload-collection `custom` config grows: + +- Added by ADR-025: `pii` (per field), `retention` (per collection) + +Per-library-trace frontmatter grows (extends ADR-022): + +- Added by ADR-025: `is-sub-processor`, `processes-pii`, `data-sent`, `region`, `dpa-signed`, `sccs-required`, `contact` + +### Conformance ESLint rule impact + +Rule count: 7 → 10. New rules at warn severity (matching the audit/analytics-event convention): + +- `no-undeclared-consent-check` — `consent.requires(...)` literal calls must match manifest's `requiresConsent` +- `no-undeclared-rate-limit` — auth/write/export categorized use cases without `rateLimit` field +- `pii-declaration-must-be-complete` — Payload `pii: true` fields missing required sub-keys (category, purpose, retention) + +### Generator + CI gate inventory + +| Generator | Source | Output | CI gate | +| ---------------------------------- | ----------------------------------------------------------- | --------------------------------- | -------------------------------------------- | +| `pnpm compliance:data-map` | Payload field `custom.pii` | `compliance/data-map.yml` | output matches collections (drift detection) | +| `pnpm compliance:retention-policy` | Payload collection `custom.retention` | `compliance/retention-policy.yml` | output matches collections | +| `pnpm compliance:sub-processors` | `docs/library-decisions/*.md` with `is-sub-processor: true` | `compliance/sub-processors.yml` | output matches traces | +| `pnpm compliance:emit-all` | runs all three | three files | runs all three CI gates | + +## Alternatives considered + +### A. One mega-epic covering all 10 items + +Single PRD, single epic, ~30-40 stories. Pros: tight coupling across the manifest-schema changes; one review surface. Cons: enormous PR backlog with no natural checkpoint; reviewer fatigue; if half-shipped, the partial state leaves an ambiguous compliance surface. + +**Rejected.** The four-epic split keeps each PRD focused enough for a useful single-document review. + +### B. Per-item ADRs (10 ADRs) + +One ADR per item — finer granularity. Pros: each architectural decision recorded in isolation. Cons: 10 ADRs to maintain, much repetition (each restates the playbook context), no unifying strategy doc, harder to answer cross-cutting questions like "why these 3 deferrals" without re-reading 4+ ADRs. + +**Rejected.** ADR-025 is the unifying strategy; per-epic PRDs handle implementation specifics. Future deepening decisions on individual items can spawn their own ADRs as needed (e.g., when DSR cascade semantics need a specific architectural call, that becomes ADR-NNN). + +### C. Wait until the first downstream consumer asks + +Don't build any of this until a consumer arrives with a real compliance requirement. + +**Rejected.** The whole value proposition of this template is that DPA/GDPR-shaped consumers don't have to invent these surfaces. The "named-consumer-now" rule from ADR-022 applies to _library adoption_, not to template surface — the consumer of this surface is "every downstream EU-bound product," which is real and immediate. + +### D. PII declared at use-case manifest level instead of Payload field level + +`pii: [{ category, purpose, retention }]` per use case, mirroring audit/publishes. + +**Rejected.** Duplicates metadata across every use case touching the same field. Wrong semantic layer — PII is a storage question, not an action question. DSR (Epic B) needs runtime access to PII tags at the field level to walk Payload collections; manifest-level tags would require synthesis at runtime. + +### E. DSR split across multiple cores (`@repo/core-data-export`, `@repo/core-data-delete`, etc.) + +Maximum granularity. Pros: consumers adopt only what they need. Cons: 4+ packages to scaffold, tight coupling in practice (delete cascade needs to know export's PII tags), overkill — each "core" would have one interface. + +**Rejected.** One `@repo/core-dsr` with 4 interfaces, mirroring `core-shared`'s tracer/logger/metrics packaging pattern (multiple interfaces in one package). Opt-in is at the package level, not the interface level. + +### F. Consent folded into `core-audit` or `core-analytics` + +`IConsent` added to an existing core. + +**Rejected.** Audit _records_, consent _gates_ — different abstractions. Conflates package purpose. Analytics is only one of many consent-gated channels (marketing, profiling, cookies, third parties); putting consent inside analytics is too narrow. + +### G. Rate-limit as interface-only (no brand) + +Just an `IRateLimit` contract. Consumers call `rateLimit.consume(...)` where they need. + +**Rejected.** Rate-limit drift would only surface when traffic hits — way too late. The five-latency drift detection is the template's signature pattern. Skipping it for rate-limit when it's universally applicable to auth/write/export endpoints (per playbook §5) leaves a real hole. Manifest declaration at the binding gate + runtime budget override gives both static enforcement and deployment flexibility. + +### H. Compliance docs all under `docs/compliance/` (no root `compliance/` directory) + +Templates and live artifacts co-located. + +**Rejected.** The template ships only the _shape_; the consumer fills in the _evidence_. Auditors expect `compliance/` at the repo root (matches playbook §16 + standard SOC 2/ISO 27001 audit prep). Splitting locations matches the template-vs-consumer mental model used throughout this session. + +## Consequences + +### Positive + +- **Compliance surface ~80% template-shipped.** From ~50% pre-ADR (audit + PII boundary + library residency + supply chain) to ~80% post-epics. Remaining 20% is consumer-scope by design. +- **Conformance pattern extended consistently.** Two new manifest fields (`requiresConsent`, `rateLimit`) plus two new collection-level `custom.*` extensions all follow the established pattern. Three new ESLint rules at warn level. Three new manifest-driven generators. New consumers learn the pattern once. +- **DPA audit posture improves materially.** Sub-processor inventory, retention policy, data map, DSR endpoints, cookie consent are concrete artifacts a regulator or customer audit expects. +- **Explicit deferrals prevent premature design.** RBAC, MFA, breach detection, Art. 22 won't be re-suggested by future agents — ADR-025 records the trigger conditions. +- **Two new optional cores** (`core-dsr`, `core-consent`) match the established pattern. Template-tiers grows by two; scaffold path is `pnpm turbo gen core-package `. +- **Rate-limit gets first-class treatment.** Fourth conformance channel; auth/write/export endpoints can't ship without a declared budget. +- **The audit ↔ DSR distinction is documented.** Future agents won't conflate the two — glossary entries plus this ADR's "Context" section make the split explicit. + +### Negative + +- **Manifest schema grows substantially.** Per-use-case fields go from 5 to 7. Per-collection `custom` config gains two extensions. Doc burden in glossary + `conformance-quickref.md` increases proportionally. +- **Conformance ESLint rule count: 7 → 10.** CLAUDE.md and quickref need rule-table updates each epic. +- **Two new optional cores to maintain.** Each needs versioning + CHANGELOG (per ADR-021). +- **`compliance/` directory becomes a new root location.** Adds a top-level directory alongside `docs/`, `packages/`, `apps/`. Consumers will see it; it's intentional but it's a new convention. +- **DSR cascade is non-trivial.** `IDataDelete` walking every Payload collection's PII fields requires Epic A's PII tags to be complete and correct. Epic B will surface gaps in Epic A's coverage during integration. +- **ADR-022 amendment.** Adding sub-processor fields to library traces is an extension to ADR-022's frontmatter spec. Existing traces need backfill (the weekly revalidation cron from ADR-023 will surface incomplete traces). +- **ADR-023 amendment.** SBOM generation step adds one workflow line; minor but counted. + +### Neutral + +- **No new CI gates beyond what generators introduce.** The three drift-detection gates (data-map, retention-policy, sub-processors) all reuse the existing CI workflow shape. +- **No vendor lock-in.** All interfaces (DSR cascade target, consent backend, rate-limit backend, security-header CSP values) remain consumer-decisions. Template ships interfaces + Noop/reference impls only. +- **Deferred items remain deferred.** ADR-025 doesn't preclude building RBAC/MFA/breach-detection/Art. 22 later — it just establishes that those decisions wait for product shape. +- **Cookie banner ships with opinionated EU-prominence defaults.** Consumers can override visual treatment but the default is the legally-defensible shape. + +## Related + +- ADR-006 — vertical feature packages (boundary tags new optional cores fit within) +- ADR-017 — OTel + observability PII boundary (the boundary `IConsent` does NOT cross — observability stays id-only) +- ADR-018 — audit + compliance (the sibling channel `core-dsr` complements without overlapping) +- ADR-022 — library evaluation policy (extended here with sub-processor frontmatter fields) +- ADR-023 — CI security + supply chain (SBOM amended here; rate-limit complements the supply-chain stack) +- ADR-024 — product analytics channel (sibling capture channel; `IConsent` from Epic B will gate `IAnalytics.track` calls in consumer products) diff --git a/docs/glossary.md b/docs/glossary.md index ceba55f..1bfee98 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -255,8 +255,30 @@ The counter/gauge/histogram interface. Same three-impl shape. The product-analytics interface, lives in `@repo/core-analytics` (optional core, scaffolded via `pnpm turbo gen core-package analytics`). Three methods: `track(event, properties?, user?)`, `identify(user)`, `pageView(path, properties?)`. Same three-impl shape as `ITracer`/`ILogger`/`IMetrics`. Used for funnel/cohort/retention analysis; structurally distinct from `IAuditLog` (which is compliance-driven) and from `IEventBus` (which is cross-feature action routing). Server-only by manifest gate; consumer wires React provider for client-side via `@repo/core-analytics/react`. _Avoid:_ confusing analytics events with cross-feature events (bus) or audit events (compliance) — they're three orthogonal capture channels. -**`withSpan`** / **`withCapture`** / **`withAudit`** / **`withAnalytics`**: -The four wrapper composers applied at DI bind time. Composition is nested with `withSpan` outermost; each layer attaches a brand: `withSpan` → `Instrumented`, `withCapture` → `Captured`, `withAudit` → `Audited` (when manifest declares `audits`), `withAnalytics` → `Analyzed` (when manifest declares `analyticsEvents`). +**`withSpan`** / **`withCapture`** / **`withAudit`** / **`withAnalytics`** / **`withConsent`** / **`withRateLimit`**: +The six wrapper composers applied at DI bind time. Composition is nested with `withSpan` outermost; each layer attaches a brand: `withSpan` → `Instrumented`, `withCapture` → `Captured`, `withAudit` → `Audited` (when manifest declares `audits`), `withAnalytics` → `Analyzed` (when manifest declares `analyticsEvents`), `withConsent` → `ConsentChecked` (when manifest declares `requiresConsent`), `withRateLimit` → `RateLimited` (when manifest declares `rateLimit`). See ADR-025 for the compliance-baseline channels. + +**DSR** (`@repo/core-dsr`): +The Data Subject Rights handler — optional core package providing four interfaces that operate on user data in response to GDPR Arts. 15–18 requests. `IDataExport` (Art. 15 access + Art. 20 portability), `IDataDelete` (Art. 17 erasure cascade), `IDataRectify` (Art. 16), `IProcessingRestriction` (Art. 18). Sibling to `core-audit` — audit _records_ access, DSR _acts_ on the underlying data. Walks Payload collections at runtime using field-level `custom.pii` tags. See ADR-025. +_Avoid:_ confusing DSR with audit; audit is the immutable journal, DSR is the mutator. + +**Consent** (`@repo/core-consent`): +The runtime gate for granular consent categories (essential/functional/analytics/marketing). Optional core package providing `IConsent` interface (`isGranted`/`grant`/`withdraw`/`getCategories`) + `ConsentChecked` brand + `requiresConsent: [...]` manifest field + `no-undeclared-consent-check` ESLint rule. Cookie banner UI ships as atomic component in `core-ui`. See ADR-025. + +**Rate-limit** (`core-shared/rate-limit`): +Fourth conformance channel after audit/analytics/consent. `IRateLimit` interface (`consume`/`reset`) + `withRateLimit` wrapper + `RateLimited` brand + `rateLimit: { window, budget }` manifest field. Default budgets in manifest, runtime overrides via `ctx.rateLimit` config. Consumer wires Redis/Upstash impl. See ADR-025. + +**PII inventory**: +Personal-data declaration at the Payload field level via `custom.pii = { category, purpose, retention, exportable, restrictable }`. Generator emits `compliance/data-map.yml`. DSR cascade walks this at runtime. See ADR-025. + +**Retention policy**: +Per-Payload-collection retention via `custom.retention = { activeRetention, postDeletion, purgeSchedule, hardDeleteAfter }`. Generator emits `compliance/retention-policy.yml`. Background purge job in `core-shared/jobs` reads this at boot. Per-PII-field retention overrides apply (more-specific wins). See ADR-025. + +**Sub-processor**: +External service that processes personal data on behalf of the controller (Stripe, SendGrid, PostHog, etc.). Declared via extended ADR-022 library traces — `docs/library-decisions/-.md` frontmatter gains `is-sub-processor`, `processes-pii`, `data-sent`, `region`, `dpa-signed`, `sccs-required`, `contact` fields. Generator emits `compliance/sub-processors.yml` from traces filtered to `is-sub-processor: true`. See ADR-025. + +**`docs/compliance/` vs `compliance/`**: +Split locations for compliance artifacts. `docs/compliance/` (template-shipped) holds FILL-IN TEMPLATES (`.template.md` runbooks, `.example.yml` references). Root `compliance/` (consumer-created) holds LIVE ARTIFACTS (`data-map.yml`, `retention-policy.yml`, `sub-processors.yml` from generators + consumer-authored filled runbooks). Audit evidence lives in `compliance/`. See ADR-025. **Span**: A traced unit of work. Emitted by `tracer.startSpan(...)` (inline in repos) or `withSpan(...)` (composed for use cases + controllers).