The audit log decision record: why the audit log model you chose determines your compliance investigation surface and your GDPR right-to-erasure conflict
Audit log decisions are made in three founding sessions that never document the consequences — the "add audit logging" session that writes application events to the main database without specifying which actions constitute an auditable event or what the immutability contract is, the "add SOC 2 controls" session that retroactively adds audit log coverage without specifying the retention period or the investigation query interface, and the "handle the GDPR erasure request" session that discovers the audit log contains the deleted user's email address in every event they performed — creating a conflict between the immutability required by compliance certification and the erasure required by privacy regulation. What none of these sessions produce is the auditable event taxonomy, the immutability model, the actor pseudonymization strategy, or the retention and query interface specification that determines whether the audit log is a compliance asset or a liability that fails the next audit.
A 28-person B2B SaaS company that sold project management software to mid-market professional services firms began preparing for SOC 2 Type II certification in month eighteen of operation. They had already passed SOC 2 Type I — a point-in-time assessment of controls — and were now in the twelve-month observation window for Type II, which requires demonstrating that the controls operate continuously over a sustained period. The auditor's evidence request included production audit logs demonstrating logical access controls: who logged into the system, who accessed which customer project records, who changed permission settings, who exported data, and who performed any administrative action during the six-month sample period the auditor had selected.
The engineering team pulled the logs from Datadog. The first problem emerged immediately: the company had been on Datadog's Flex Logs tier with a 30-day retention window. The six-month audit period the auditor had specified ran from January through June. The Datadog retention window meant that January, February, and March events — three of the six months — had been automatically deleted. The four engineers who had added audit log calls to the codebase over the previous eighteen months had all done the same thing: they called the existing structured logging library with an audit: true tag, which sent the event to Datadog alongside the application's debug logs, performance traces, and error events. No one had configured a separate log stream with a longer retention policy. No one had specified that audit events, unlike operational logs, must be retained for the duration of any potential compliance audit period plus a buffer. The decision to use Datadog for audit events had been made implicitly, by using the logging infrastructure that already existed, without asking whether that infrastructure's retention policy met the compliance requirement.
The second problem was coverage gaps. The auditor's checklist referenced specific event types required by SOC 2 CC6.1 (logical access changes — user account creation, deactivation, permission grant, permission revoke) and CC6.8 (data activity monitoring — records accessed, exported, modified). When the engineering team searched the Datadog logs for events matching these types, they found inconsistent coverage: login and logout events were logged (the engineer who built authentication had added log calls), but permission changes were not (the engineer who built the permission management UI had not added audit log calls, because there was no specification that said they needed to), and data exports were partially logged (three of the four export code paths had log calls, but a fourth path — CSV export from a recently shipped bulk-download feature — did not). The auditor's checklist was comprehensive; the audit log coverage was not, because coverage had accumulated through individual engineers' judgment about what was important to log rather than through a specification of what the compliance framework required. The engineering team spent three weeks rebuilding the audit log infrastructure — separate storage, full retention, correct coverage — before the certification window could proceed.
A 35-person e-commerce infrastructure company processed GDPR right-to-erasure requests through a manual workflow: the privacy team received the request, an engineer ran a script that deleted the user's account row, nulled out their personal data fields in the application tables, and marked the request as processed in the privacy log. The engineer had built the erasure script eighteen months earlier during a "GDPR compliance" sprint. The script covered the tables they knew about at the time. Audit log coverage of the erasure itself was added — the script wrote a record saying "user account erased on date X per request Y." The process worked for forty erasure requests over eighteen months.
In month twenty-two, the company's newly hired DPO reviewed the erasure process as part of an internal compliance audit. She ran the erasure script on a test account in the staging environment and then inspected all tables for remaining personal data. The audit log table — audit_events in the main PostgreSQL database — contained 847 rows referencing the test user's email address. The audit event schema was: {"id": uuid, "actor_email": "user@example.com", "action": "order.status_changed", "resource_id": "order:ord_xxx", "resource_type": "order", "timestamp": "..."}. The actor_email field contained the performing user's email address — the natural human-readable identifier that the engineer had chosen when building the audit log in the founding session. The erasure script deleted the user from the users table and cleared their email from the application tables, but it did not touch the audit_events table, because the audit events were meant to be an immutable record.
The DPO identified the conflict immediately. Under GDPR Article 17, the user had exercised their right to erasure of personal data. The email address in actor_email was personal data — it directly identified the user. The audit_events table contained 847 instances of that personal data that had not been erased. But the audit_events table was the company's primary compliance record — altering it would destroy the evidentiary value of 847 audit events, leaving gaps in the access log that a future SOC 2 auditor might flag. The engineer who built the audit log schema had not asked or been asked whether the actor identifier should be a stable, opaque token rather than the user's email address. The choice of actor_email as the identifier was made in the founding session, under the reasoning that email was the identifier everyone used to refer to users — it was in the user table, it was in the support tickets, it was in the CRM. The conflict between its presence in the audit log and the GDPR erasure requirement was not visible until eighteen months later, when the DPO ran the inspection.
Structural properties set by the audit log design decision
Three structural properties are determined when a team decides how to implement audit logging. None appear explicitly in the founding session that adds the first audit log calls — they are the operational consequences of choices made under the pressure of getting the feature shipped before the next compliance review or sales call.
Property 1: The auditable event taxonomy and the immutability contract. An audit log is not a comprehensive record of everything the application does. It is a compliance record of actions that have a material effect on the security or data integrity state of the system — specifically, the actions that compliance frameworks (SOC 2, HIPAA, PCI DSS, GDPR Article 30) require to be logged. The auditable event taxonomy is the enumerated list of those actions, organized by resource type and operation, with the compliance justification for each entry.
The taxonomy distinguishes between audit events and operational log events. Operational log events — slow query warnings, cache misses, HTTP request timings, background job completions — are diagnostic information that helps engineers understand system behavior. They can be deleted after 30 to 90 days because their purpose is real-time debugging and short-term incident analysis. Audit events — user login, record access, permission change, data export, payment processed, API key generated — are compliance evidence that demonstrates security controls are operating correctly. They must be retained for the duration of the longest compliance framework's requirement: SOC 2 Type II typically requires 12 to 13 months of coverage; HIPAA requires 6 years; PCI DSS requires 12 months with the most recent 3 months immediately available. The retention requirement means audit events cannot be stored in the same infrastructure as operational logs if that infrastructure has a rotation policy configured for operational logging purposes.
The immutability contract specifies what "cannot be modified or deleted" means at the storage level. A PostgreSQL table with UPDATE and DELETE privileges granted to the application database user is not immutable — any application bug, SQL injection vulnerability, or operator error can modify or delete audit events. The immutability contract must be enforced at the database level (a table with no UPDATE or DELETE grants for the application user, or a database user that has INSERT privileges only), at the application level (the audit log writer is a separate service or library that has no read-modify-write path — only append), and at the infrastructure level (point-in-time backup of the audit log storage with the backup retention configured to exceed the audit log retention requirement, so that the audit log can be restored from backup if it is corrupted or the storage is lost). The data retention decision record documents the retention policy for application data; the audit log retention policy must be specified separately, because the compliance driver (SOC 2, HIPAA, PCI DSS) determines the minimum retention period and that minimum may be longer than the application data retention period.
The taxonomy also determines the audit event schema: the minimum set of fields that every audit event must contain to be useful for compliance investigation. The required fields are: a unique, immutable event identifier (UUID, not an auto-increment integer — auto-increment IDs can be predicted or inferred, which creates a gap in the audit trail if events are deleted and the gaps are not visible in the primary key sequence); the actor token (an opaque identifier for the human or system principal that performed the action — discussed in detail in the next property); the action identifier (a structured string that identifies the event type: user.login, payment.processed, permission.granted, data.exported — a taxonomy-consistent naming scheme enables the query interface to filter by event type across all resources); the resource type and resource identifier (the type of the entity the action was performed on — order, customer, permission, api_key — and its stable identifier within that type); the timestamp at which the action occurred (UTC, with millisecond precision, set by the audit log writer at the time the event is recorded — not the timestamp from the request, which can be manipulated by a client); and the outcome (success or failure, with an error code for failures, to distinguish between a user who attempted to access a record and failed due to access control and a user who successfully accessed it).
Property 2: The actor identity model and the GDPR right-to-erasure conflict. Every audit event must identify the actor — the human user, service account, or automated process that initiated the action. The actor identifier must be durable for the retention period of the audit log: if the audit log is retained for 6 years (HIPAA) and the actor identifier is the user's email address, the audit log contains the user's email address for 6 years after they perform any audited action, regardless of whether they have since deleted their account or exercised their right to erasure.
The conflict between audit log immutability and GDPR right-to-erasure is not an edge case — it is a predictable consequence of using personal data as an actor identifier in a system that must satisfy both a compliance framework (which requires immutable records) and a privacy regulation (which requires erasure of personal data on request). The conflict does not arise if the actor identifier is not personal data. The resolution is actor pseudonymization: the audit log stores an opaque actor token — a UUID generated at account creation time, derived from no observable attribute of the user, stored in the user record and used exclusively in the audit log — rather than the user's email address, name, or any other personal identifier.
The actor token is accompanied by a separate lookup table that maps each actor token to the user's current human-readable identifiers (email, display name) for rendering in the audit log viewer. The authentication strategy decision record documents the user identity model; the audit log actor token must be a separate identifier from the authentication session token (which is rotated on logout and expiry) and from the user's application UUID (which may be used in application data relationships and exposed in URLs — linking it to the audit log creates a correlation path that reduces the pseudonymization benefit). When the audit log viewer renders events, it joins the actor token against the lookup table. When a GDPR erasure request is processed, the lookup table entry is deleted. The audit events remain intact — the actor token is still present in every event, but the token no longer resolves to a human-readable identity in the display layer. The audit trail retains its evidentiary value: the sequence of actions, the attribution of each action to a specific actor token, and the access to specific resources are all preserved. The personal data — email, name — is gone.
The actor identity model must also address system actors — processes that perform actions without a human initiating them. Background jobs that process payments, scheduled tasks that purge expired sessions, automated processes that provision resources in response to user actions — these actors appear in the audit log and must be identifiable, but they are not human users and do not have GDPR erasure rights. System actors are identified by a service identifier (payment-processor-job, session-cleanup-service) rather than a user token, and the audit log schema must distinguish between human actor events (actor type: user, actor token: uuid) and system actor events (actor type: service, actor service: identifier) to support investigation queries that filter by actor type. The authorization model decision record documents the permission model for both human users and service accounts; the audit log actor identity model must align with the authorization model's notion of principal, because access control investigations require querying audit events by the principal whose permissions are under review.
Property 3: The retention policy and the compliance investigation query interface. The retention policy specifies the minimum duration for which audit events must be retained in a queryable form, not merely archived. A tape backup of audit events satisfies the technical letter of a retention requirement — the data is preserved — but fails the spirit of a compliance investigation requirement if retrieving the backed-up data takes 48 hours. SOC 2 auditors request evidence on a deadline. Security investigators need to query the audit log during a live incident. The retention policy must specify not just how long events are kept, but how long they are kept in a form that supports the required query access patterns with acceptable latency.
The compliance investigation query interface specifies the access patterns that auditors and security investigators use to query the audit log, and the index coverage required to support those patterns efficiently. The required query patterns are: all events by a specific actor in a time window (used to investigate whether a specific user accessed resources they should not have, or to produce evidence of a user's access history for a SOC 2 auditor); all events on a specific resource in a time window (used to produce a complete access history for a specific record — who accessed the customer record for Acme Corp between January 1 and March 31); all events of a specific type in a time window (used to audit all login failures across all accounts, all permission changes, all data exports); all events in a narrow time window regardless of actor or resource (used for incident timeline reconstruction — what happened between 14:00 and 14:15 UTC on the day of the breach); and compound queries combining actor, resource type, event type, and time range.
These access patterns require composite index coverage on (actor_token, timestamp), (resource_type, resource_id, timestamp), and (action, timestamp). An audit log table without these indexes will scan sequentially for each query — a table with 50 million events takes minutes per query without indexes, which makes real-time investigation impossible and turns every evidence production request into an engineering project. The database vendor decision record documents the database choice for the application; the audit log storage choice must be evaluated separately against the query access patterns, because the application database that works well for transactional workloads (many small reads and writes) may not work well for the audit log's query workload (infrequent but large scans over months of data, plus point-in-time lookups by multiple dimensions). A dedicated audit log storage system — PostgreSQL with append-only access and the required composite indexes, or a purpose-built audit log service — may serve the investigation query interface better than co-locating audit events in the application's primary database where they compete for index cache with application queries.
What the founding session records and what it omits
Audit logging is almost always implemented in multiple phases: an initial phase when the team realizes they need some form of access logging (often triggered by a security incident, a customer request, or a first SOC 2 engagement), a second phase when the first compliance review reveals gaps in coverage (specific event types required by the compliance framework are not being logged), and a third phase when the first GDPR erasure request or security investigation reveals problems with the event schema or the query infrastructure. None of these phases produces an audit log ADR. Each phase patches the specific problem that motivated it, leaving the broader design — the auditable event taxonomy, the immutability contract, the actor identity model, the retention policy, the investigation query interface — undocumented and discoverable only through the next compliance failure.
Three types of AI chat sessions generate these gaps:
The "we need audit logging" session. The trigger is usually a combination of a customer asking "do you have an audit trail?" during a security questionnaire, a sales deal that requires SOC 2 compliance documentation, or an internal security incident that prompts an "we should be logging this" retrospective. The engineer asks how to add audit logging to the application. The session explains how to add log calls to the relevant code paths, suggests a structured JSON format for the events, and recommends a logging provider. What the session does not ask or answer: which specific actions constitute auditable events and which are operational log events — the distinction between a slow-query warning (operational) and a permission change (audit) is obvious in retrospect but not specified in the session, which means coverage is determined by which engineer added log calls to which code paths; what the retention requirement is for the compliance frameworks the company is subject to, and whether the chosen logging provider's retention configuration satisfies that requirement; what the immutability contract for audit events is, and whether the storage model enforces it (a logging provider that allows log deletion via API is not immutable at the storage level — it is only immutable by convention, which can be broken by an operator with API access or by a bug in a log-management automation script); what the actor identifier should be, and whether using the user's email address creates a future GDPR conflict; and whether the audit log needs a separate query interface from the operational logging search, or whether the operational logging provider's search will be sufficient for compliance investigation queries at the scale the audit log will reach after twelve months of growth. The logging strategy decision record documents the team's approach to operational logging; the audit log strategy must be a separate decision record, not an extension of the operational logging strategy, because the requirements diverge: operational logs are deleted to control cost, audit logs are retained for compliance; operational logs are queryable by anyone with engineering access, audit logs require controlled access with an authorization model; operational logs can be in any format that the logging provider supports, audit logs must follow a schema that produces evidence usable in a compliance review.
The "SOC 2 says we need to log X" session. This session occurs during the first SOC 2 engagement, when the auditor or the compliance team provides a list of event types that must be logged. The engineer asks how to add audit log coverage for the specific event types on the list. The session explains how to instrument the relevant code paths. What the session does not ask or answer: whether the existing audit log infrastructure — storage, retention, immutability — is appropriate for the additional events, or whether the infrastructure itself needs to be redesigned; how the new events integrate with the existing event schema, and whether the new events use consistent field names and action identifier conventions (a schema where some events use actor_email and others use user_id and others use actor_token, because each was added in a different session, is not a coherent audit log — it is a collection of inconsistently structured log calls that happens to cover the required event types); what the query interface is for the auditor to review the evidence, and whether the current logging provider's search supports the required access patterns at the current event volume; whether the new events introduce any additional GDPR conflicts (logging the IP address of every request, for example, may satisfy the auditor's requirement for access logging but introduces personal data into the audit log that will conflict with future erasure requests); and what the process is for maintaining audit log coverage as the application evolves — specifically, how a team ensures that when a new code path is added that performs an auditable action, an engineer adds the audit log call, and when an existing code path is refactored, the audit log call is preserved. The compliance automation decision record documents the approach to automating compliance checks; the audit log coverage verification — a test or CI check that enumerates the auditable event types in the taxonomy and verifies that each type has at least one test that produces an audit event — must be part of the compliance automation rather than a manual review that happens only when the auditor asks for evidence.
The "we got a GDPR erasure request and the audit log has their email" session. This session occurs when an engineer or DPO realizes, mid-erasure or post-erasure, that the audit log contains personal data that was not included in the erasure script. The engineer asks how to handle the conflict between the GDPR erasure requirement and the audit log immutability requirement. The session may suggest erasing the personal data from the audit log (which destroys its evidentiary value), may suggest keeping the data (which fails the erasure requirement), or may explain the pseudonymization approach (which is the correct resolution but requires migrating the existing audit log schema). What the session does not produce: a documented actor identity model that prevents the conflict from reappearing for the next user who requests erasure; a migration plan for existing audit events that use email addresses as actor identifiers; a specification of which fields in the audit log schema contain personal data that must be considered in future erasure requests (the IP address field, if present, is also personal data under GDPR in most interpretations); a process for testing the erasure script against the audit log to verify that it addresses all personal data fields (a test that runs the erasure script on a test account and then scans all tables for any remaining occurrence of the test user's email address, including the audit log, the lookup table, and any derived tables that may cache actor display data); and a review of what other fields in the audit log schema may contain personal data embedded in the resource identifier, the action context, or the event metadata (a resource_id of email-address:alice@example.com is personal data in the resource identifier field; a context.search_query field that captures the search term a user entered is personal data if the search query contains names or email addresses). The data governance decision record documents the data classification and handling policy; the audit log schema must be reviewed against the data classification to ensure that no personal data field appears as a structural field (rather than an opaque identifier) in the audit event schema, and that any personal data that appears in dynamic fields (context, metadata, search queries) is handled consistently with the data governance policy.
The WhyChose extractor surfaces the "we need audit logging" session, the "SOC 2 says we need to log X" session, and the "GDPR erasure request" session from AI chat history. The audit log ADR converts the implicit choices in those sessions — using email as the actor identifier, storing audit events in the same logging provider as operational logs, adding log calls to specific code paths without a taxonomy — into a documented auditable event taxonomy that specifies which actions are audit events and which are operational log events, a documented immutability contract that specifies the storage-level enforcement of append-only access, a documented actor identity model that prevents the next engineer from using personal data as an actor identifier, and a documented retention and query interface specification that ensures the audit log can produce compliance evidence without an engineering sprint every time an auditor asks for it.
The five sections of an audit log ADR
Section 1: Auditable event taxonomy and event schema. Document the auditable event taxonomy as an explicit, versioned enumeration of event types, organized by resource type. For each resource type (user accounts, customer records, payment records, permissions, API keys, data exports, configuration settings, integrations), enumerate the operations that produce audit events: the CRUD operations that are in scope (user.created, user.deactivated, user.deleted), the access operations that require logging (record.accessed, record.exported), the permission operations (permission.granted, permission.revoked), the authentication events (session.login, session.logout, session.login_failed, api_key.generated, api_key.revoked), and the administrative operations (config.changed, integration.connected, integration.disconnected).
For each event type in the taxonomy, document the compliance justification: which SOC 2 criterion, HIPAA section, PCI DSS requirement, or GDPR article requires this event to be logged. SOC 2 CC6.1 requires logging of logical access changes (user account creation, deactivation, permission grants and revocations); SOC 2 CC6.2 requires logging of authentication events (login, logout, failed login); SOC 2 CC6.8 requires logging of data access and modification; SOC 2 CC7.2 requires logging of security events (failed authentication, access denied events, configuration changes). If the compliance justification is not documented, the team cannot evaluate whether a new feature introduces an auditable action without reviewing the full compliance framework — which means new auditable events are added by whoever reviews the SOC 2 audit evidence report and notices a gap, not by whoever adds the feature that creates the auditable action.
Document the canonical event schema as a JSON structure with required and optional fields. Required fields on every audit event: event_id (UUID v4, generated by the audit log writer), actor_type (enum: user, service, system), actor_token (UUID for user actors, service identifier string for service actors), action (string from the taxonomy's action identifier list), resource_type (string from the taxonomy's resource type list), resource_id (the stable, opaque identifier of the resource — not the display name, which may change, and not a personal data value), outcome (enum: success, failure), recorded_at (UTC timestamp with millisecond precision, set by the audit log writer). Optional fields: ip_address (if IP logging is required by the compliance framework — note that IP addresses are personal data under GDPR and must be pseudonymized or excluded from the actor lookup table erasure path), user_agent (if session context is required), context (a JSON object for event-specific metadata that does not fit the standard fields — the schema for each event type's context must be documented in the taxonomy so that the context field is not a catch-all for arbitrary unstructured data). The event-driven architecture decision record documents whether the application uses an event bus for domain events; audit events should not be delivered through the same event bus as domain events, because the audit log writer must guarantee at-least-once delivery to the audit log storage without depending on the event bus's delivery guarantees — a domain event that fails to deliver can be retried or skipped depending on the business logic; an audit event that fails to deliver is a gap in the compliance record.
Section 2: Immutability model and write path. Document the immutability model as a set of enforcement mechanisms at each layer of the stack. At the database level: the audit log table is created with INSERT permission granted to the application database user and no UPDATE or DELETE permission; a PostgreSQL row-level security policy on the table enforces this at the database level, not just the application level, so that a SQL injection vulnerability in the application cannot modify audit events (because the application database user cannot execute UPDATE or DELETE on the table regardless of the SQL it sends). At the application level: the audit log writer is a separate service or library — not a general-purpose database client that the application uses for other tables — with a single public method (append an event) and no read-modify-write path; the audit log writer is not allowed to query the audit log table, because the audit log is write-only from the application's perspective (reading audit logs is done through a separate investigation interface with its own access controls). At the infrastructure level: the audit log database is configured with point-in-time recovery backups retained for the duration of the audit log retention requirement plus a buffer (if the audit log retention is 13 months, backups are retained for 15 months); the backup configuration is tested quarterly by restoring a backup to a test environment and verifying that audit events from the backup match the events in the primary database.
Document the write path from action occurrence to audit log record. The write path must be synchronous within the request that performs the auditable action, or must guarantee delivery with a durable queue if asynchronous. A fire-and-forget async write (the application writes the audit event to a queue and returns the response to the user without waiting for the event to be persisted) is acceptable only if the queue provides durable, at-least-once delivery — not a best-effort in-memory queue that loses events on process restart. If the audit event write fails — database is unavailable, network timeout — the request must still complete, but the failure must be logged as an alert (not a debug log) and the system must have a mechanism for detecting and recovering missed audit events. A missed audit event is a gap in the compliance record; a gap during the SOC 2 audit period is a finding. Document the failure handling: whether the application retries audit event writes, whether it fails the originating request when the audit event cannot be written (appropriate for high-security actions where proceeding without an audit trail is unacceptable), and the maximum acceptable gap in the audit log before an alert is fired. The observability strategy decision record documents the alerting infrastructure; the audit log write failure alert must be a separate alert class from operational error alerts, because an audit log gap during a SOC 2 audit period is a compliance incident, not an operational incident, and the response procedure is different.
Section 3: Actor identity model and privacy-compliance integration. Document the actor identity model with the pseudonymization specification and the GDPR integration. The actor token for human users is generated at account creation time: a UUID v4 generated by a cryptographically secure random number generator, stored in the user record as audit_actor_token (distinct from the user's application UUID and from any authentication token), and never changed for the lifetime of the user record. The token is opaque — it has no structure that reveals the user's identity, the account creation date, or any other attribute. The token is stored in the user record alongside the user's email and name; when the user's account is deleted or an erasure request is processed, the token is preserved in a separate pseudonymization ledger (a table with only actor_token and erased_at columns, no personal data) so that the audit log system knows the token corresponds to an erased user without retaining the mapping from token to personal data.
Document the actor lookup table schema and the erasure procedure. The lookup table maps each actor token to the user's current display data: (actor_token UUID, display_name TEXT, display_email TEXT, display_avatar_url TEXT). The lookup table is the only place in the system where an actor token is associated with personal data. When the audit log viewer renders events, it joins the event's actor_token against the lookup table using a LEFT JOIN — events whose actor token has no lookup table entry (because the user has been erased) display as "Deleted user" or a configurable placeholder. The erasure procedure for the audit log: (1) delete the lookup table row for the erased user's actor token; (2) insert a row in the pseudonymization ledger recording the token and the erasure date; (3) verify that no remaining occurrence of the erased user's email, name, or other personal identifiers appears in the audit log schema fields (the structured fields — actor_token, action, resource_type, resource_id — are verified by schema; the context JSON field requires a schema-aware scan because it contains event-specific metadata that may include personal data depending on the event type). Document which event types have context fields that may contain personal data and how those fields are handled in the erasure procedure.
Document the access control model for the actor lookup table. The lookup table contains personal data — the email addresses and names of every user who has ever performed an audited action. The table must have more restrictive access controls than the audit event table: only the audit log viewer service and the erasure processor have read access; the application's general database user does not have access; the table is excluded from database dumps used for non-production environments. The access control model decision record documents the authorization framework; the audit log access control must specify separately who can view raw audit events (security team, compliance team), who can view the audit log viewer that resolves actor tokens to human-readable names (broader internal stakeholders), and who can query the audit log for investigation purposes (security team with a documented access request process).
Section 4: Retention policy and storage model. Document the retention policy as a compliance-framework-specific minimum: the longest retention requirement among all frameworks the company is subject to determines the minimum retention period for all audit events. If the company is subject to SOC 2 (13 months minimum for Type II audit coverage), GDPR (no explicit retention period for audit logs, but 'as long as necessary for the documented purpose'), and PCI DSS (12 months with most recent 3 months immediately available), the minimum is 13 months with the most recent 3 months in hot storage (queryable with sub-second latency) and the full 13 months in warm storage (queryable with acceptable latency for investigation, not necessarily sub-second). The data retention decision record documents the application data retention policy; the audit log retention policy is an addendum that overrides the general policy for audit event records and must be reviewed whenever the company adds a new compliance framework.
Document the tiered storage model for the audit log. Hot tier: the most recent N months of audit events are stored in the primary audit log database with full index coverage and sub-second query latency. The N value is determined by the compliance framework that requires the most recent data to be "immediately available" — PCI DSS Requirement 10.7 requires the most recent 3 months to be immediately available for analysis. Warm tier: audit events older than the hot tier threshold are moved to a lower-cost storage system — a separate PostgreSQL database with the same schema and index coverage as the hot tier, but with less compute allocated (because query frequency is lower); or an append-only object storage system (S3-compatible) with Athena or a similar query layer for investigation queries that can tolerate higher latency. The warm tier must support the same query access patterns as the hot tier, even if query latency is higher (minutes rather than seconds). Cold tier: archived audit events for compliance frameworks with very long retention requirements (HIPAA's 6-year requirement means 6 years × 12 months = 72 months of events if HIPAA applies) are stored in the cheapest durable storage available (Glacier or equivalent) with a documented retrieval process. The retrieval process must be tested annually to verify that archived events can be retrieved and queried within a defined SLA — a retrieval SLA of "48 hours to restore from Glacier" must be documented so that compliance investigators who need archived evidence know the timeline before they trigger the retrieval.
Document the storage model's write-path guarantees. If the hot-tier audit log database becomes unavailable — planned maintenance, hardware failure, network partition — what happens to audit events that are generated during the outage? The options are: block the originating action (the user cannot perform the audited action until the audit log is available — appropriate for the highest-security actions, where proceeding without an audit trail is a compliance violation); buffer in a durable queue (the application writes audit events to a durable queue that persists them to the audit log database when it recovers — appropriate for most audit events, where a short gap during database maintenance is acceptable as long as all events are eventually delivered); or accept the loss (the audit event is dropped if the database is unavailable — never acceptable for compliance events, though sometimes used for operational log events). The secrets management decision record documents the credential management for the application's database connections; the audit log database credentials must be managed separately from the application database credentials, because the audit log database's access controls are more restrictive and the credentials must not be accessible to application code paths that do not interact with the audit log.
Section 5: Investigation query interface and access controls. Document the investigation query interface as a defined API (internal, not customer-facing in most cases) with the required query parameters and the response format. The query interface must support: actor query (return all events where actor_token equals a given value, optionally filtered by time range, action type, and resource type); resource query (return all events where resource_type and resource_id match given values, optionally filtered by time range and action type); action query (return all events where action matches a given value or pattern, optionally filtered by time range); time range query (return all events within a given time window, with optional actor, resource, and action filters); and compound queries combining any of the above with AND logic. The query interface must return events in reverse chronological order by default (most recent first), with pagination using a cursor rather than an offset (an offset-based pagination on a table of 50 million events requires the database to scan and skip the first N rows for every page after the first; a cursor-based pagination uses the last event's timestamp and ID as the cursor, which the database can seek to directly using the timestamp index).
Document the access control model for the investigation query interface. The audit log itself records who accessed which resources — the investigation interface must also be access-controlled and must generate audit events for its own use (an investigator who queries the audit log is performing a privileged access action that is itself auditable: audit_log.queried with the investigator's actor token and the query parameters). Access to the investigation interface must follow the principle of least privilege: engineers should not have ad-hoc access to the raw audit log via the application database — they should query through the investigation interface, which enforces the access control model and generates an audit trail of the investigation itself. A security incident investigation where the investigator's queries are not themselves audited creates a second-order problem: if the investigator's account is later determined to have been compromised, there is no record of which audit events they queried. The authorization model decision record documents the permission model; the investigation query interface must integrate with the authorization model so that access is granted through the same permission system used for the rest of the application, with a specific role (audit_log_viewer, audit_log_investigator) rather than direct database credentials.
Document the customer-facing audit log, if applicable. Many B2B SaaS products surface a subset of the internal audit log to customers — an activity log in the customer's account settings that shows who on their team logged in, accessed records, or changed settings. The customer-facing audit log is a subset of the internal audit log, filtered to events that belong to the customer's tenant and that are safe to surface to the customer (internal system events and cross-tenant events are excluded). The customer-facing audit log display must resolve actor tokens to human-readable names using the lookup table — but the customer's DPO may ask for their own users' audit events to be erased, which creates the same GDPR conflict as the internal case. The pseudonymization model must apply consistently to the customer-facing display layer. If a customer user requests erasure, the lookup table entry for their actor token is deleted, and the customer-facing audit log displays "Deleted user" for their events — the same behavior as the internal viewer. Document whether customers can export their audit log subset (if yes, the export format must not expose actor tokens, which are internal identifiers — the export must use display names for events where the actor is still in the lookup table, and "Deleted user" for erased actors), how long the customer-facing audit log is retained (may be shorter than the full internal retention period), and whether the customer-facing audit log is included in data subject access requests (GDPR Article 15 requires the company to provide a data subject with a copy of all personal data held about them — if the audit log contains the data subject's actor token resolved to their personal data via the lookup table, the activity log is personal data that must be included in the DSAR response).
Further reading
- Data retention decision record — the retention policy framework that the audit log retention overrides for compliance-driven event types.
- Compliance automation decision record — automating audit log coverage verification so gaps are detected before the auditor does.
- Data governance decision record — data classification that determines which audit log fields require personal data handling.
- Authentication strategy decision record — the user identity model that the audit log actor token must be distinct from.
- Authorization model decision record — the permission framework for the investigation query interface access control.
- Database vendor decision record — the storage infrastructure decision that the audit log may need to diverge from for its specific query workload.
- Logging strategy decision record — the operational logging infrastructure that audit events must be separated from.
- Event-driven architecture decision record — the event bus that audit events should not share delivery guarantees with.
- Observability strategy decision record — the alerting infrastructure that audit log write failures must be surfaced through.
- WhyChose extractor — surface the "add audit logging," "SOC 2 controls," and "GDPR erasure" sessions from your AI chat history and convert them into an audit log ADR.