# Compliance evidence exportable for legal review

- **Pattern:** `ab-000793` (`compliance-consent-engine.compliance-audit-trail.evidence-export`)
- **Severity:** info
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000793
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000793)

## Why it matters

GDPR Article 5(2) accountability and Article 30 records-of-processing requirements both assume that a controller can produce evidence of compliance on request — but "we have the data in the database" is not an evidence export mechanism. Without admin tooling that joins consent records, audit log entries, and erasure receipts into a coherent timeline, responding to a data protection authority inquiry or a data subject access request requires a bespoke database query every time, introducing inconsistency and delay. ISO 27001:2022 A.5.35 covers evidence of compliance activities. CCPA §1798.185(a)(8) requires businesses to document their data practices in a producible form.

## Severity rationale

Info because the gap affects operational efficiency and legal defensibility but does not itself cause a data subject's rights to be violated — compliance tooling is the means, not the obligation.

## Remediation

Add a `GET /api/admin/contacts/:id/compliance-report` endpoint that joins all three compliance data sources in one call:

```ts
// src/app/api/admin/contacts/[id]/compliance-report/route.ts
export async function GET(req: Request, { params }: { params: { id: string } }) {
  const [consentRecords, auditLog, erasureLog] = await Promise.all([
    db.consentRecord.findMany({ where: { contactId: params.id }, orderBy: { createdAt: 'asc' } }),
    db.auditLog.findMany({ where: { entityId: { startsWith: params.id } }, orderBy: { createdAt: 'asc' } }),
    db.erasureAuditLog.findMany({ where: { contactId: params.id }, orderBy: { processedAt: 'asc' } }),
  ])

  return Response.json({
    generatedAt: new Date().toISOString(),
    contactId: params.id,
    consentHistory: consentRecords,
    operationalAuditTrail: auditLog,
    erasureRequests: erasureLog,
  })
}
```

Protect this route with admin-only authentication and log every export to the audit trail itself — an unauthenticated compliance export is itself a data exposure incident.

## Detection

- **ID:** `evidence-export`
- **Severity:** `info`
- **What to look for:** When regulators or legal counsel need to review compliance history for a specific contact, check whether the system can produce a structured export of all relevant compliance events: consent grants, revocations, opt-out processing timestamps, data subject requests handled, and erasure receipts. This should be producible by an operator without a direct database query. Look for an admin export endpoint, a support tool, or a documented process.
- **Pass criteria:** An operator can export the complete compliance history for a contact (consent records + audit log entries + erasure receipts) through an admin interface or documented API endpoint, without needing raw database access. Count all data sources included in the export — at least 3 must be joined (consent records, audit log, erasure log).
- **Fail criteria:** Producing compliance evidence requires a manual database query with no tooling. Or the export exists but is incomplete (e.g., only the consent records, not the audit log showing who made changes).
- **Skip (N/A) when:** No audit log exists (fails earlier check).
- **Detail on fail:** `"No admin export for compliance history — legal team must request raw SQL queries for each GDPR inquiry"` or `"Admin export shows consent_records only, omitting audit_log entries and erasure_audit_log"`
- **Remediation:** Add a compliance export endpoint:

  ```ts
  // GET /api/admin/contacts/:id/compliance-report
  export async function getComplianceReport(contactId: string) {
    const [consentRecords, auditLog, erasureLog] = await Promise.all([
      db.consentRecord.findMany({ where: { contactId }, orderBy: { createdAt: 'asc' } }),
      db.auditLog.findMany({ where: { entityId: { startsWith: contactId } }, orderBy: { createdAt: 'asc' } }),
      db.erasureAuditLog.findMany({ where: { contactId }, orderBy: { processedAt: 'asc' } }),
    ])

    return {
      generatedAt: new Date().toISOString(),
      contactId,
      consentHistory: consentRecords,
      operationalAuditTrail: auditLog,
      erasureRequests: erasureLog,
    }
  }
  ```

## External references

- gdpr Art. 5(2)
- gdpr Art. 30
- iso-27001 A.5.35
- ccpa §1798.185(a)(8)

Taxons: regulatory-conformance, observability

HTML version: https://auditbuffet.com/patterns/ab-000793
