docs: introduce EU compliance baseline strategy (ADR-025)
ADR-025 plans 4 epics that raise the template's DPA/GDPR coverage from ~50% (ADR-017/018/022/023/024) to ~80%. Adds two optional cores (core-dsr, core-consent), a fourth conformance channel (rate-limit), three new manifest fields, three new generators with CI drift gates, and a docs/compliance/ + compliance/ split for templates vs evidence. Documents the audit ↔ DSR sibling-not-overlap distinction (audit records access; DSR acts on the underlying data). Four explicit deferrals with revisit triggers (RBAC, MFA, breach detection, Art. 22). Glossary updated with DSR, Consent, Rate-limit, PII inventory, Retention policy, Sub-processor, docs/compliance/ vs compliance/ entries.
This commit is contained in:
345
docs/decisions/adr-025-eu-compliance-baseline.md
Normal file
345
docs/decisions/adr-025-eu-compliance-baseline.md
Normal file
@@ -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<UserDataBundle>;
|
||||
}
|
||||
interface IDataDelete {
|
||||
deleteSubjectData(
|
||||
subjectId: string,
|
||||
mode: "soft" | "cascade-hard",
|
||||
): Promise<DeletionCertificate>;
|
||||
}
|
||||
interface IDataRectify {
|
||||
updateSubjectField(
|
||||
subjectId: string,
|
||||
collection: string,
|
||||
field: string,
|
||||
value: unknown,
|
||||
): Promise<void>;
|
||||
}
|
||||
interface IProcessingRestriction {
|
||||
setRestriction(subjectId: string, granted: boolean): Promise<void>;
|
||||
isRestricted(subjectId: string): Promise<boolean>;
|
||||
}
|
||||
```
|
||||
|
||||
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<boolean>;
|
||||
grant(
|
||||
subjectId: string,
|
||||
categories: ConsentCategory[],
|
||||
record: ConsentRecord,
|
||||
): Promise<void>;
|
||||
withdraw(subjectId: string, categories: ConsentCategory[]): Promise<void>;
|
||||
getCategories(subjectId: string): Promise<ConsentCategory[]>;
|
||||
}
|
||||
```
|
||||
|
||||
`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" })`).
|
||||
|
||||
**`<CookieConsentBanner>` 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<void>;
|
||||
}
|
||||
```
|
||||
|
||||
- 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 <name>`.
|
||||
- **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)
|
||||
@@ -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/<date>-<pkg>.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).
|
||||
|
||||
Reference in New Issue
Block a user