Evidence, not a badge
GDPR Compliance Matrix
The machine-verifiable map from GDPR obligation to the control implemented in this repository. test_compliance_matrix.py asserts that every cited code path and test exists, that every roadmap row carries a date, and that no cell contains an overstated compliance claim. MedLineage acts as a processor; rows marked controller describe what the software provides *to* the controller, who remains responsible for their own obligations. This document supports an audit conversation; it is not legal advice.
Status vocabulary: implemented | partial | roadmap(YYYY-MM).
| Article | Obligation | Role | Control | Code | Tests | Status |
|---|---|---|---|---|---|---|
| Art. 5(1)(b) | Data collected for specified explicit purposes only | shared | Consent rows carry an explicit purpose; external-output routes check consent per purpose when MEDRECORD_CONSENT_ENFORCEMENT_ENABLED is on (posture depends on deployment flag) | consent.py | test_consent.py | implemented |
| Art. 5(1)(c) | Data minimization | processor | Audit log rejects free-text inputs; verification-demand ledger stores graph ids and enums only; agent audit stores content hashes, never raw payloads; structured logs carry tenant hash, never raw patient ids | audit.py, verification_demand.py, agent_runtime.py | test_audit.py, test_verification_demand.py | implemented |
| Art. 5(1)(d) | Accuracy; inaccurate data corrected | shared | Clinician attestation ledger (correct/incorrect/unclear verdicts), demand-weighted review prioritization, extraction-corrections store | ledger.py, verification_demand.py, migrations/0015_extraction_corrections.sql | test_ledger.py | implemented |
| Art. 5(1)(e) | Storage limitation | processor | Offline opt-in retention sweeper over packets, feedback JSONL, and (explicit opt-in) access_log; cadence set by operator per docs/GDPR/RETENTION.md | retention.py, docs/GDPR/RETENTION.md | test_stage1_gdpr.py | partial |
| Art. 6 | Lawful basis for processing | controller | Controller determines the basis; the software records a basis string on consent rows and the Connect ingest surface carries a legal_basis field per push | consent.py, api/connect_v1_routes.py | test_consent.py | implemented |
| Art. 7(1) | Demonstrable consent | shared | POST /api/consent writes a consent row plus an append-only consent_event trail | consent.py | test_consent.py, test_api/test_api_consent_audit.py | implemented |
| Art. 7(3) | Withdrawal as easy as giving consent | shared | POST /api/consent/withdraw; subsequent gated reads are denied and the denial is logged | consent.py, audit.py | test_consent.py, test_api/test_api_consent_audit.py | implemented |
| Art. 9(2) | Special-category (health) data conditions | controller | Health data processed only under controller instruction; the DPA template's processing annex scopes categories and purposes | docs/GDPR/DPA_TEMPLATE.md | - | implemented |
| Art. 12 | Transparent information to data subjects | shared | Intended-use disclaimer rendered on PDF page 1, frontend not-medical-advice modal, public PRIVACY.md | ui_strings.py, export.py, PRIVACY.md | test_intended_use.py | implemented |
| Art. 15 | Right of access (SAR) | processor | GET /api/audit/access?patient_id= projects the per-patient access history; per-patient Data Room and account export produce the data copy | audit.py, data_room.py, account_data.py | test_audit.py, test_data_room.py | implemented |
| Art. 16 | Rectification | processor | Operator-mediated: attestation verdict incorrect plus extraction-corrections store; no patient self-service rectification API | ledger.py, migrations/0015_extraction_corrections.sql | test_review_console.py | partial |
| Art. 17 | Erasure | processor | delete_patient hard-deletes every patient-scoped table plus feedback JSONL; POST /api/connect/erase for partner-driven erasure; access_log retained under Art. 17(3)(b) | patient_graph.py, account_data.py | test_api/test_account_data.py, test_erasure_cascade.py | implemented |
| Art. 18 | Restriction of processing | processor | No restriction mechanism yet; erasure and consent withdrawal are the available levers | - | - | roadmap(2026-10) |
| Art. 20 | Portability (structured, machine-readable) | processor | Byte-deterministic Data Room tar.gz with canonical JSON and a FHIR R4 bundle; tenant-scoped account export | data_room.py, fhir.py | test_data_room.py | implemented |
| Art. 21 | Right to object | controller | Controller-side decision; the software provides the withdrawal and erasure channels the controller invokes | consent.py, account_data.py | test_consent.py | implemented |
| Art. 22 | No solely-automated decisions with legal or similar effect | processor | Non-diagnostic by design: deterministic scoring, safety guardrails redact violating sections, recommendation-verb lints on every user-facing surface | safety.py, scoring.py | test_safety.py | implemented |
| Art. 25 | Data protection by design and by default | processor | All persistence surfaces default OFF; pseudonymous HMAC-derived patient ids; content-addressed ids carry no PHI; per-purpose signing-key domain separation | config.py, graph_ids.py, signing.py | test_graph_ids.py, test_signing.py | implemented |
| Art. 28 | Processor contract | shared | Fill-in DPA template plus sub-processor schedule shipped with the product | docs/GDPR/DPA_TEMPLATE.md, docs/GDPR/SUBPROCESSORS.md | - | implemented |
| Art. 30 | Records of processing (ROPA) | processor | One access_log row per external-output access, allowed or denied, with flag-off short-circuit rows so enabling enforcement later leaves no gap; GET /api/audit/access is the ROPA view | audit.py | test_audit.py, test_api/test_api_consent_audit.py, test_api/test_connect_v1_summary.py | implemented |
| Art. 32 | Security of processing | processor | PBKDF2 operator credentials, HMAC-signed ledger with boot-time secret guard, per-IP rate limiting, upload validation, HSTS when forced HTTPS, tenant partition isolation; no encryption at rest yet | accounts.py, signing.py, api/ratelimit.py, api/uploads.py, tenant.py, docs/GDPR/TOMS.md | test_auth.py, test_api/test_security.py, test_multitenant_isolation.py | partial |
| Art. 32(1)(c) | Restore availability after incident | shared | Backup and restore runbook for the SQLite state | docs/GDPR/BACKUP_RESTORE.md | - | implemented |
| Art. 33, 34 | Breach notification (authority, data subjects) | shared | Operator runbook template; structured JSON logs and optional Sentry for detection; no automated notification tooling | docs/GDPR/INCIDENT_RESPONSE.md, config.py | - | partial |
| Art. 35 | DPIA | controller | DPIA support template plus data-flow narrative maintained alongside the code | docs/GDPR/DPIA.md, docs/GDPR/DATA_FLOW.md | - | implemented |
| Art. 44-49 | Third-country transfers | shared | Sub-processor schedule documents each transfer (Anthropic API) and its safeguard; operator chooses deployment region | docs/GDPR/SUBPROCESSORS.md | - | implemented |
Open gaps, ranked by cost/value
Art. 5(1)(e) sweep coverage — the retention sweeper covers packets, feedback, and access_log; newer tables accumulate outside it. roadmap(2026-09).
Art. 16 self-service — rectification is operator-mediated today. A patient-facing channel is a product decision, not scheduled.
Art. 32 encryption at rest — deliberate V1 boundary (partition-key separation, not crypto). roadmap(2026-12).
Art. 33/34 tooling — runbook only; automated breach-notification tooling unscheduled.
Art. 18 restriction — roadmap(2026-10).
Notes: comms_inbound is keyed by phone + business_ref (no patient_id column); its erasure path is the business_ref delete, invoked from the comms lifecycle, and is out of the patient-cascade's mechanical scope.