diff --git a/.claude/skills/evaluate-library/SKILL.md b/.claude/skills/evaluate-library/SKILL.md index be0b24b..a44722d 100644 --- a/.claude/skills/evaluate-library/SKILL.md +++ b/.claude/skills/evaluate-library/SKILL.md @@ -178,13 +178,69 @@ Name at least two alternatives evaluated before choosing this library. For `core --- +## Sub-processor classification + +Answer these two questions before writing the trace. The answers become +top-level frontmatter fields required by ADR-022 §9. + +### Question: is-sub-processor + +**Does the vendor receive personal data on the operator's behalf?** + +A library is a sub-processor when it transmits personal data (user identifiers, +email addresses, behavioural events, request bodies, etc.) to a vendor-controlled +endpoint — analytics SDKs, error-tracking clients, AI APIs, log aggregation +services. Network calls alone do not make a library a sub-processor; only calls +that carry personal data do. + +Pure in-process libraries (no network calls), self-hostable software where the +operator controls the endpoint, and build-time-only tools are **not** sub-processors. + +Set `is-sub-processor: true | false`. + +### Question: processes-pii + +**Does the library process personal data in-process, even without transmitting +it to a vendor?** + +A library processes PII if it reads, validates, serialises, stores, or +transforms data fields that may contain personal information (names, emails, +IDs, content authored by users, authentication credentials). A self-hosted +database or CMS is a prime example: no data leaves to a vendor, yet the +library clearly handles PII. + +Pure utility libraries (DI containers, type validators, serialisers operating +on already-typed objects without inspecting field semantics, test runners) +typically answer `false`. + +Set `processes-pii: true | false`. + +### Conditional block: when is-sub-processor is true + +When `is-sub-processor: true`, five additional fields are **required** in the +trace frontmatter. Gather them before writing the trace: + +``` +data-sent: "" +region: "" +dpa-signed: true | false # has the operator signed a DPA with this vendor? +sccs-required: true | false # does the vendor require SCCs (non-EEA transfer)? +contact: "" +``` + +If the vendor does not yet have a signed DPA or if you cannot determine the +region, record `dpa-signed: false` / region as best-known and add a prose note +under `## Sub-processor` in the trace body explaining the gap. + +--- + ## Trace write step Write the trace **unconditionally** at evaluation end — even for rejections, even for partial traces. **Path:** `docs/library-decisions/-.md` -Use today's date. Use the `TRACE-TEMPLATE.md` in this directory as the structural guide. +Use today's date. Use `docs/library-decisions/_template.md` as the structural guide. Frontmatter rules: @@ -192,8 +248,46 @@ Frontmatter rules: - `adr: null` for feature-tier. For core-tier approvals, coordinate the ADR slug before writing (`adr: adr-NNN`). - `verification-commands` — include the literal commands run for each filter, one per line. - `accepted-cves: []` (empty unless you accepted a specific advisory). +- `is-sub-processor` and `processes-pii` are **always required** (see Sub-processor classification above). +- When `is-sub-processor: true`, include `data-sent`, `region`, `dpa-signed`, `sccs-required`, and `contact`. - For skipped expensive filters, write `skip` for the frontmatter value and omit the prose section body or note "Not evaluated — skipped due to earlier rejection." +Frontmatter template: + +```yaml +--- +package: +version: "" +tier: app | feature | core +decision: approved | rejected +date: +deciders: [, ...] +adr: adr-NNN | null +lastRevalidated: null +is-sub-processor: false +processes-pii: false +# include the block below only when is-sub-processor: true +# data-sent: "" +# region: "" +# dpa-signed: false +# sccs-required: false +# contact: "" +filter-results: + license: + types: native | "@types/" | none + maintenance: active | dormant | abandoned + boundary-fit: pass | fail + shadow-check: pass | fail | "shadows " + eu-residency: ok | n/a | self-hostable | fail + cve-scan: clean | "" | fail + named-consumer: pass | fail + socketRisk: clean | flagged | +verification-commands: + - +accepted-cves: [] +--- +``` + After writing the trace: - For approved traces: confirm the trace is staged in the same commit as the `package.json` change. The pre-commit hook validates this. diff --git a/docs/decisions/adr-022-library-evaluation-policy.md b/docs/decisions/adr-022-library-evaluation-policy.md index 3ccbb1f..91e54dc 100644 --- a/docs/decisions/adr-022-library-evaluation-policy.md +++ b/docs/decisions/adr-022-library-evaluation-policy.md @@ -196,6 +196,52 @@ Every existing runtime dependency in feature- and core-tier packages ADR-002, ADR-014, ADR-017 are cited via the `adr:` frontmatter field; verification-command output is captured at backfill time. +### 9. Sub-processor discriminated union (amendment: 2026-05-18) + +Every trace carries two top-level frontmatter fields classifying the library +from a GDPR sub-processor perspective: + +```yaml +is-sub-processor: false # boolean — true when the vendor receives personal data on the operator's behalf +processes-pii: false # boolean — true when the library processes PII in-process (even without transmitting it) +``` + +When `is-sub-processor: true`, five additional fields are **required**: + +```yaml +data-sent: "" +region: "" +dpa-signed: true | false +sccs-required: true | false +contact: "" +``` + +**Discriminated-union rules:** + +| `is-sub-processor` | `processes-pii` | Conditional fields required? | +| ------------------ | --------------- | --------------------------------------------------------------------------------- | +| `false` | `false` | No — pure library, no data involvement | +| `false` | `true` | No — in-process only, no vendor data flow | +| `true` | `true` | Yes — all five conditional fields required | +| `true` | `false` | Technically possible but very unusual; still requires all five conditional fields | + +**Baseline for backfill:** pure in-process libraries (no network calls to +vendor-controlled endpoints) get `is-sub-processor: false` + `processes-pii: false`. +Self-hosted software that stores PII but transmits nothing to the vendor (e.g. +`payload`) gets `is-sub-processor: false` + `processes-pii: true`. + +These fields are the machine surface consumed by `scripts/emit-sub-processors.mjs` +(see Story 06 of the compliance-manifests-pii-retention-subprocessors epic). A +trace missing `is-sub-processor` is treated as `false` by the generator for +backward-compatibility; all new traces authored after this amendment must include +both fields. The `evaluate-library` skill (§7) prompts for these fields +unconditionally and writes the conditional block only when `is-sub-processor: true`. + +The weekly `dpa-signed` staleness check in CI (ADR-023 cross-reference) should +flag any `dpa-signed: true` traces where the DPA has not been revalidated within +the prior 365 days. Implementation of that cron is deferred to the CI security +hardening work. + ## Alternatives considered - **No policy, keep relying on instinct.** Rejected. The 2026-05-14 OpenAPI diff --git a/docs/library-decisions/2026-05-14-globals.md b/docs/library-decisions/2026-05-14-globals.md index 760e3d9..9fe45bf 100644 --- a/docs/library-decisions/2026-05-14-globals.md +++ b/docs/library-decisions/2026-05-14-globals.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: null +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: MIT types: native diff --git a/docs/library-decisions/2026-05-14-inversify.md b/docs/library-decisions/2026-05-14-inversify.md index 596bdd1..7d48f2b 100644 --- a/docs/library-decisions/2026-05-14-inversify.md +++ b/docs/library-decisions/2026-05-14-inversify.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: adr-002 +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: MIT types: native diff --git a/docs/library-decisions/2026-05-14-payload.md b/docs/library-decisions/2026-05-14-payload.md index 27a034a..778a557 100644 --- a/docs/library-decisions/2026-05-14-payload.md +++ b/docs/library-decisions/2026-05-14-payload.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: null +lastRevalidated: null +is-sub-processor: false +processes-pii: true filter-results: license: MIT types: native diff --git a/docs/library-decisions/2026-05-14-react-dom.md b/docs/library-decisions/2026-05-14-react-dom.md index e51bdf2..ed3437f 100644 --- a/docs/library-decisions/2026-05-14-react-dom.md +++ b/docs/library-decisions/2026-05-14-react-dom.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: null +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: MIT types: native diff --git a/docs/library-decisions/2026-05-14-react.md b/docs/library-decisions/2026-05-14-react.md index 8a1b783..2a0a314 100644 --- a/docs/library-decisions/2026-05-14-react.md +++ b/docs/library-decisions/2026-05-14-react.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: null +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: MIT types: native diff --git a/docs/library-decisions/2026-05-14-reflect-metadata.md b/docs/library-decisions/2026-05-14-reflect-metadata.md index b31a79d..1d1dde0 100644 --- a/docs/library-decisions/2026-05-14-reflect-metadata.md +++ b/docs/library-decisions/2026-05-14-reflect-metadata.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: adr-002 +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: Apache-2.0 types: native diff --git a/docs/library-decisions/2026-05-14-superjson.md b/docs/library-decisions/2026-05-14-superjson.md index ce4385b..067e71c 100644 --- a/docs/library-decisions/2026-05-14-superjson.md +++ b/docs/library-decisions/2026-05-14-superjson.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: null +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: MIT types: native diff --git a/docs/library-decisions/2026-05-14-vitest.md b/docs/library-decisions/2026-05-14-vitest.md index 778250f..b7ebfda 100644 --- a/docs/library-decisions/2026-05-14-vitest.md +++ b/docs/library-decisions/2026-05-14-vitest.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: null +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: MIT types: native diff --git a/docs/library-decisions/2026-05-14-zod.md b/docs/library-decisions/2026-05-14-zod.md index 9b02746..c528a42 100644 --- a/docs/library-decisions/2026-05-14-zod.md +++ b/docs/library-decisions/2026-05-14-zod.md @@ -6,6 +6,9 @@ decision: approved date: 2026-05-14 deciders: [Danijel Martinek] adr: null +lastRevalidated: null +is-sub-processor: false +processes-pii: false filter-results: license: MIT types: native diff --git a/docs/library-decisions/_template.md b/docs/library-decisions/_template.md index f25d9a7..fbfbade 100644 --- a/docs/library-decisions/_template.md +++ b/docs/library-decisions/_template.md @@ -7,6 +7,14 @@ date: deciders: [, ...] adr: adr-NNN | null lastRevalidated: null +is-sub-processor: false +processes-pii: false +# include the block below only when is-sub-processor: true +# data-sent: "" +# region: "" +# dpa-signed: false +# sccs-required: false +# contact: "" filter-results: license: types: native | "@types/" | none