Compliance Ledger Taxonomy (Draft)
The compliance ledger is the append-only record of privacy/security-significant events. Each event is a single row in compliance_ledger with:
event_type(string)subject_id(UUID, optional)metadata(JSON; small, stable keys only)tenant_id(scope; enforced by RLS)
Current Events (implemented)
| Event Type | Produced By | Subject ID | Required Metadata |
|---|---|---|---|
privacy.user.soft_deleted | User delete handler (DELETE /users/\{id\}) | User ID | action (soft_delete), job_type, subject_user_id |
privacy.user.anonymized | PrivacyJobsWorker (anonymize_user job) | User ID | job_id, job_type, status, subject_user_id, occurred_at, actor_type, actor_id?, receipt_ref, artifact_hash, action (anonymize_user), reason? |
privacy.retention.anonymize | PrivacyJobsWorker (anonymize_user job with reason=retention) | User ID | Same as privacy.user.anonymized; reason = retention; emitted by retention worker. |
privacy.user.forgotten | PrivacyJobsWorker (forget_user job) | User ID | job_id, job_type, status, subject_user_id, occurred_at, actor_type, actor_id?, receipt_ref, artifact_hash, action (hybrid_scrub), assetsDeleted, textRedacted, legalBasis? |
privacy.dsar.completed | PrivacyJobsWorker (dsar_export job) | User ID | job_id, job_type, status, subject_user_id, artifact_ref, receipt_ref, occurred_at, actor_type, actor_id?, legalBasis? |
privacy.dsar.failed | PrivacyJobsWorker (dsar_export job failure) | User ID (if known) | job_id, job_type, status=failed, subject_user_id?, error, occurred_at |
privacy.evidence.deleted | PrivacyJobsWorker (evidence_delete job) | Evidence subject ID | job_id, job_type, status, subject_user_id, occurred_at, actor_type, actor_id?, deleted |
privacy.user.unlinked | PrivacyJobsWorker (unlink_user job) | User ID | job_id, job_type, status, subject_user_id, org_unit_id, roles_removed, remaining_roles, orphaned, occurred_at, actor_type, actor_id?, reason?, ack_orphan_risk?, orphan_risk_flag? |
privacy.user.orphaned | PrivacyJobsWorker (unlink completion) or orphan sweep | User ID | subject_user_id, org_unit_id?, detected_at, reason (unlinked/orphan_sweep), occurred_at? (metadata timestamp) |
assignment.override.changed | Assignment override upsert | Assignment ID | overrideId, userId, extraAttempts, timeLimitExtension, reason |
assignment.schedule.run | Assignment schedule runner | Assignment ID | scheduleId, runLabel |
remediation.apply | Remediation apply | Batch ID | batchId, rescored, appliedAt, reason, evaluation |
remediation.revert | Remediation revert | Batch ID | batchId, revertBatchId, rescored, revertedAppliedAt |
results.reap | Delivery reaper auto-submit | (none) | closed, grace, runMs, scope, tenant, started |
certificate.issued | Certificate issuance | Certificate ID | programId, enrolmentId, userId, issuedAt, expiresAt |
privacy.jobs.failed | Privacy jobs worker (any job) | Job ID | job_id, job_type, status=failed, subject_user_id?, error, occurred_at |
Common metadata field names (privacy events)
job_id(UUID) — privacy_jobs.id that produced the ledger row.job_type—anonymize_user|forget_user|dsar_export|evidence_delete|restrict_user|unlink_user.subject_user_id— the user being acted on.tenant_id,org_unit_id?— scope identifiers (also on the row).occurred_at— UTC timestamp when work completed.actor_type—USERorSYSTEM;actor_idpresent whenUSER.receipt_ref— storage URI for the JSON receipt;artifact_reffor DSAR exports.artifact_hash— SHA256 of the receipt payload.reason?— free-form reason (e.g.,retention,user_request).action— canonical action label (anonymize_user,hybrid_scrub,place,lift).roles_removed,remaining_roles,orphaned,orphan_detected_at?— unlink/orphan context.
Required Events (to be implemented)
These must be added to achieve full coverage. Producers should emit a ledger row using CreateComplianceLedgerEntry with tenant-scoped context:
| Event Type | Source/Flow | Subject ID | Metadata (suggested keys) |
|---|---|---|---|
privacy.retention.failed | Retention/anonymization failure (worker or job) | User ID | job_id, job_type, error, reason (retention), subject_user_id |
Emission rules
- Emit inside the scoped transaction (via
TxManager) so RLS tenant isolation applies. - Keep metadata small and stable; avoid embedding full payloads or PII beyond identifiers already scoped by RLS.
- Prefer explicit IDs and summary fields over raw request bodies or large blobs.
Verification
- API:
/api/v1/compliance/ledger?limit=N(requiresprivacy.manage) should surface these events. - Tests should assert that each high-stakes flow writes the expected event type with the required metadata keys.
Status: Draft — only the “Current Events” are wired today; “Required Events” must be implemented in upcoming work.