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.
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.
Add a GET /api/admin/contacts/:id/compliance-report endpoint that joins all three compliance data sources in one call:
// 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.
ID: compliance-consent-engine.compliance-audit-trail.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:
// 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,
}
}