Skip to main content

Audit Trails and Automatic Retention

Automate logging and lifecycle transitions so compliance is enforced "by default," not by convention.

Audit Trails

Security & Compliance Events to Log

EventDescription
viewDocument accessed/viewed
downloadDocument downloaded
createDocument created
updateDocument modified
deleteDocument deleted
status_changeLifecycle state transition
permission_changeAccess permissions modified
retention_changeRetention policy modified
hold_appliedLegal hold placed
hold_removedLegal hold released

Required Fields Per Event

FieldDescription
event_idUnique event identifier
timestampWhen event occurred (UTC)
user_idWho performed action
session_idUser session identifier
actionEvent type
doc_idAffected document
old_valuePrevious value (if applicable)
new_valueNew value (if applicable)
source_ipClient IP address
deviceDevice identifier
decisionAllow/deny result

Storage Requirements

RequirementDescription
Append-onlyCannot modify existing logs
Tamper-evidentCryptographic verification
WORM/log-structuredWrite-once storage
Blockchain-styleOptional for HIPAA/GDPR
RetainedLogs retained per regulatory requirements

Schema Reference

Data Structure

field_name:
type: string
required: true
description: Field description
example: "example_value"

API Reference

Endpoint Overview

MethodEndpointDescription
GET/api/v1/resourceList resources
POST/api/v1/resourceCreate resource
PUT/api/v1/resource/:idUpdate resource
DELETE/api/v1/resource/:idDelete resource

Retention Policies

Central Retention Catalog

CREATE TABLE retention_policies (
retention_category TEXT PRIMARY KEY,
description TEXT NOT NULL,
duration_years INT NOT NULL,
trigger_event TEXT NOT NULL, -- "effective_date", "account_closure"
legal_basis TEXT NOT NULL -- "HIPAA", "SEC 17a-4"
);

Example Categories

CategoryDurationTriggerLegal Basis
HIPAA-6Y6 yearseffective_dateHIPAA 45 CFR 164.316(b)(2)(i)
SEC-7Y7 yearseffective_dateSEC Rule 17a-4
FINRA-6Y6 yearseffective_dateFINRA 4511
HR-7Y7 yearstermination_dateState employment law

Document Linking

Each document links to a retention category:

retention_category: "HIPAA-6Y"
effective_date: "2025-01-01"
# System computes: retain_until = 2031-01-01

Automated Retention Jobs

Background Jobs

  1. Scan for documents past retain_until
  2. Check not on legal hold
  3. Action:
    • Move to archive
    • Anonymize
    • Destroy (with approval if required)
FieldDescription
legal_holdBoolean flag
hold_reasonWhy hold was placed
hold_ownerWho placed the hold
hold_dateWhen placed

Legal hold prevents deletion regardless of retention expiry.

UI for Hold Management

  • View all documents on hold
  • Place/release holds
  • Track hold history
  • Generate hold reports

Automation Patterns

Event-Driven Triggers

EventAction
status_change → approvedSet effective_date, compute review_due_date, recalc retain_until
retention_category changedRecompute retain_until
review_due_date approachingGenerate review task

Integration with Tasking

  • Generate review tasks ahead of review_due_date
  • Alert document owners
  • Escalate overdue reviews

Retention Update SQL Trigger

CREATE OR REPLACE FUNCTION recompute_retain_until()
RETURNS TRIGGER AS $$
DECLARE
pol retention_policies;
years INT;
BEGIN
SELECT * INTO pol
FROM retention_policies
WHERE retention_category = NEW.retention_category;

IF NOT FOUND THEN
RAISE EXCEPTION 'Unknown retention_category: %', NEW.retention_category;
END IF;

years := GREATEST(pol.duration_years, pol.min_years);

IF NEW.effective_date IS NULL THEN
RAISE EXCEPTION 'effective_date required for retention computation';
END IF;

NEW.retain_until := (NEW.effective_date + (years || ' years')::INTERVAL)::DATE;

RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Destruction Queue

CREATE TABLE destruction_queue (
id BIGSERIAL PRIMARY KEY,
doc_id UUID NOT NULL,
queued_at TIMESTAMPTZ NOT NULL DEFAULT now(),
processed_at TIMESTAMPTZ,
status TEXT NOT NULL DEFAULT 'queued',
reason TEXT NOT NULL
);

-- Daily job: enqueue eligible docs
INSERT INTO destruction_queue (doc_id, reason)
SELECT dm.doc_id, 'retention_expired'
FROM document_metadata dm
WHERE dm.legal_hold = FALSE
AND dm.retain_until <= CURRENT_DATE
AND NOT EXISTS (
SELECT 1 FROM destruction_queue dq
WHERE dq.doc_id = dm.doc_id
AND dq.status IN ('queued','processing','completed')
);

References