Type to search pages and rules.

Browse pages

Audit Trail

An audit trail is evidence that one actor attempted one action against one target, and what the attempt produced.

Markdown44 provisions

Intent#

An audit trail is evidence that one actor attempted one action against one target, and what the attempt produced. It answers who is accountable.

An audited system keeps three records with different producers, readers, and retention. Conflating them removes evidence.

RecordHoldsRead by
Diagnostic logWhat the software didEngineers during an incident
Domain event streamWhat became trueThe system itself
Audit trailWho is accountable for an attemptReviewers, customers, regulators

A domain event cannot substitute for an audit record. A domain event carries domain identity rather than use-case identity. One event type can come from more than one use case. A domain event carries only the fields the domain needs. An actor the domain ignores is absent. A refused attempt changes no state and raises no event. Refusal is the case a reviewer asks about.

Activation#

Activation scope: project.

Applicable specification kinds: None.

The consumer enables audit when a person or a machine acts inside data that another party owns, and a later reviewer must establish accountability. Regulatory obligation, customer contract, payment scope, and platform-side support access each meet this condition.

Audit obligations maps each provision on this page to the external obligation it satisfies.

Baseline relationship#

This extension replaces litespec/quality/security#record-security-audit-events. The baseline states the required record fields for one security audit event. This extension owns the producer, the transaction boundary, the selection policy, the store, and the read path.

This extension switches on no optional record type.

Standards#

Record the audit adoption decision#

ruleguidance

Requirement An audit adoption decision MUST record its owner, status, decider, decide-by, audited categories, threshold, access model, integrity method, and records entry.

Rationale The selection, its retention, and its protection are policy choices. A reviewer evaluates the declared policy before evaluating the implementation. No record is approved while this decision is draft. Each audited category carries its retention period and lawful basis. The decision records the bulk-read threshold, the read access model, the integrity method, and the processing-records entry.

litespec/ext/audit#record-the-audit-adoption-decision

Declare audit selection on every Command#

ruleguidance

Requirement Every Command MUST declare that it is audited, or declare that it is excluded together with the reason for exclusion.

Rationale An implicit selection cannot be reviewed. A total declaration makes an omission a visible statement rather than an absent call.

Example A storefront browse Command declares exclusion because a public catalog read is not a sensitive action.

litespec/ext/audit#declare-audit-selection-on-every-command

Cover the required audit categories#

ruleguidance

Requirement An audit selection MUST include authentication, authorization denial, grant change, monetary action, tenant configuration, personal-data read, bulk export, and background-process change.

Rationale These categories carry the questions a security review, a customer, and a supervisory authority ask. A trail that omits one of them cannot answer its own purpose.

litespec/ext/audit#cover-the-required-audit-categories

Exclude indiscriminate auditing#

ruleguidance

Requirement A project MUST NOT audit every Command without a recorded selection.

Rationale An undifferentiated trail buries the sensitive action inside routine traffic. Volume then defeats the review the trail exists to support.

litespec/ext/audit#exclude-indiscriminate-auditing

Emit audit records at the mediation boundary#

ruleguidance

Requirement An audit producer MUST run in the mediation pipeline stages that dispatch the Command.

Rationale Only the pipeline observes the whole attempt, including its outcome. A handler that throws never reaches an audit call placed inside it.

Example A pre-handler opens the audit scope, a post-handler closes it as succeeded, and an error handler closes it as denied or failed.

litespec/ext/audit#emit-audit-records-at-the-mediation-boundary

Restrict handler contribution to determined values#

ruleguidance

Requirement A handler MUST contribute only the audit values that the handler alone determines.

Rationale A generated target identity and a composed reason are unavailable before the handler runs. Every other field comes from the declaration, the actor, and the request context.

Example A create handler contributes the identity it generated. It contributes no actor, action code, or timestamp.

litespec/ext/audit#restrict-handler-contribution-to-determined-values

Exclude derived audit trails#

ruleguidance

Requirement An audit trail MUST NOT be derived from domain events, database triggers, or change data capture.

Rationale These sources describe committed state change. They carry no refused attempt, no use-case identity, and no request origin.

litespec/ext/audit#exclude-derived-audit-trails

Record the required audit fields#

ruleguidance

Requirement An audit record MUST contain actor, action, target, outcome, business time, record time, and tenant, fixed where the tenancy extension is absent.

Rationale These fields carry the accountability question. Business time states when the action happened. Record time states when the evidence was written. Actor and target each carry kind and identity.

litespec/ext/audit#record-the-required-audit-fields

State the action as use-case identity#

ruleguidance

Requirement An audit action MUST use the identity of the use case that the actor invoked.

Rationale A reviewer resolves the record to the specification that defines the intended behavior. A separate vocabulary requires a mapping that drifts.

Example catalog.add-product names both the use case and the audited action.

litespec/ext/audit#state-the-action-as-use-case-identity

Record the failure code on an unsuccessful attempt#

ruleguidance

Requirement An audit record with a denied or failed outcome MUST carry the stable failure code that the attempt produced.

Rationale The code distinguishes a refused permission from an exhausted precondition, which are different review findings.

litespec/ext/audit#record-the-failure-code-on-an-unsuccessful-attempt

Version the audit record shape#

ruleguidance

Requirement An audit record MUST carry an explicit schema version.

Rationale A retained record outlives several shapes of the application that wrote it. The version keeps an old record readable without rewriting it.

litespec/ext/audit#version-the-audit-record-shape

Use a closed actor set#

ruleguidance

Requirement An audit actor MUST be one of person, company, provider, system, or guest.

Rationale An absent actor collapses three answers into one. A scheduled process acted, a device acted, or no one recorded the actor. Only the third is a defect. A device maps to the person whose account authorized it. The device appears as the client description. A named process maps to system.

Example A warehouse handheld records the person actor whose account authorized it. The origin record names the device.

litespec/ext/audit#use-a-closed-actor-set

Record delegated administrative access#

ruleguidance

Requirement An action that an actor takes under an administrative or support grant MUST record that the grant was used.

Rationale A supplier-side action and a customer-side action are indistinguishable without this field. The distinction is what makes a no-silent-access commitment demonstrable.

litespec/ext/audit#record-delegated-administrative-access

Record every attempt outcome#

ruleguidance

Requirement An audit record MUST carry an outcome of succeeded, denied, or failed.

Rationale A trail of successes is a change history. The refused attempt carries the security signal.

Example succeeded names a committed change, denied names a refused authorization, and failed names a permitted attempt that did not complete.

litespec/ext/audit#record-every-attempt-outcome

Record refused authorization#

ruleguidance

Requirement An authorization decision that refuses an audited action MUST produce an audit record.

Rationale Repeated refusal is the primary indicator of a compromised or probing account. No other record holds it.

litespec/ext/audit#record-refused-authorization

Record failed audited attempts#

ruleguidance

Requirement An audited action that the actor was permitted to take and that did not complete MUST produce an audit record.

Rationale A permitted attempt that fails repeatedly separates an operating defect from an access problem.

litespec/ext/audit#record-failed-audited-attempts

Record the request origin#

ruleguidance

Requirement An audit record produced from an inbound request MUST carry the request origin address, the client description, and the request identifier.

Rationale Two records naming one account state nothing about location. The same records from two countries within one minute state that the account is compromised.

litespec/ext/audit#record-the-request-origin

Record the operation trace identity#

ruleguidance

Requirement An audit record MUST carry the trace identifier of the operation that produced it.

Rationale The identifier joins the evidence record to the diagnostic detail. An investigation without it stops at the audit record.

litespec/ext/audit#record-the-operation-trace-identity

Exclude secrets and payment data#

ruleguidance

Requirement An audit record MUST NOT contain a credential, token, session identifier, encryption key, or payment instrument value.

Rationale A retained, widely readable, append-only store is the worst location for a secret. Retention then extends the exposure.

litespec/ext/audit#exclude-secrets-and-payment-data

Exclude personal data beyond actor identity#

ruleguidance

Requirement An audit record MUST NOT contain personal data other than the pseudonymous identifier of the actor and the target.

Rationale A record holding a name, an address, or a message body turns retention into an erasure conflict. An identifier keeps the record meaningful and erasable.

A pseudonymous identifier is still personal data where the party holding it can reverse it. That is the position the EDPB states. The identifier is therefore safe inside the tenant that owns the mapping. It is a disclosure when it leaves that tenant.

Example An audit export crossing a tenant boundary replaces each identifier with one derived for that export. The receiving party then cannot join the records back to a subject.

litespec/ext/audit#exclude-personal-data-beyond-actor-identity

Exclude state snapshots#

ruleguidance

Requirement An audit record MUST NOT contain a copy of the state before or after the audited change.

Rationale The domain event stream already holds the change under its own retention. The trail adds the actor and the reason, which nothing else holds.

litespec/ext/audit#exclude-state-snapshots

Commit a success record with its business change#

ruleguidance

Requirement An audit record with a succeeded outcome MUST commit in the same database transaction as the change it describes.

Rationale One transaction makes the action and its evidence inseparable. Neither an unrecorded action nor a record of a rolled-back action can exist.

litespec/ext/audit#commit-a-success-record-with-its-business-change

Write an unsuccessful record outside the failed transaction#

ruleguidance

Requirement An audit record with a denied or failed outcome MUST be written on a connection that the failing transaction does not roll back.

Rationale The transaction that would carry the record is the one being abandoned. The evidence of a refusal outlives the refusal.

litespec/ext/audit#write-an-unsuccessful-record-outside-the-failed-transaction

Stage the audit write before the business commit#

ruleguidance

Requirement An audit producer that stages a success record MUST run before the pipeline stage that commits the business transaction.

Rationale A record staged after the commit reaches a closed transaction. Pipeline ordering therefore carries an explicit priority rather than a registration order.

litespec/ext/audit#stage-the-audit-write-before-the-business-commit

Publish unsuccessful write failures#

ruleguidance

Requirement An audit deployment MUST publish the write failure count of its unsuccessful-outcome path.

Rationale The separate connection accepts a rare lost record at process death. Continuous loss is a defect. Only a published indicator separates the two.

litespec/ext/audit#publish-unsuccessful-write-failures

Scope every record to its tenant#

ruleguidance

Requirement An audit record describing an action inside tenant-owned data MUST carry the identity of that tenant.

Rationale The tenant is the filter for every read of the trail. A trail without it serves an internal reviewer and no customer.

litespec/ext/audit#scope-every-record-to-its-tenant

Enforce append-only through storage privilege#

ruleguidance

Requirement An audit store MUST refuse update and delete through the privileges granted to the writing account.

Rationale An absent code path is a convention. A reviewer can verify a revoked privilege.

Example The application account holds INSERT and SELECT on the audit table. It holds neither UPDATE nor DELETE.

litespec/ext/audit#enforce-append-only-through-storage-privilege

Provide tamper evidence#

ruleguidance

Requirement An audit store MUST chain each record to its predecessor with a SHA-256 hash over a canonical serialization of both.

Rationale A chain detects an edit and a removal, because a changed or missing record breaks every following value. Detection is the obligation. Prevention at a higher cost is not required.

The algorithm is named because two implementations that each choose their own cannot verify each other. A verifier written later cannot recompute a chain whose algorithm no one recorded. Canonical serialization is named for the same reason. A hash over a serialization that reorders fields verifies nothing.

Example The chain value is SHA-256(previousHash || canonical(record)), where canonical sorts object members and uses one fixed encoding for every timestamp and number.

litespec/ext/audit#provide-tamper-evidence

Anchor the audit chain outside its own store#

ruleguidance

Requirement An audit store SHOULD publish a periodic checkpoint of its chain head to a destination that the store's own operators cannot rewrite.

Deviation: A store whose recorded threat model excludes its own operators publishes no checkpoint.

Rationale A chain proves that records are consistent with each other. It does not prove that the whole chain was not recomputed, which anyone holding the store can do. A checkpoint someone else holds fixes the chain as it stood at that moment. A later recomputation disagrees with it.

Example The checkpoint is the chain head and its record count, published at a stated interval. A separate append-only destination, a timestamping authority, and a transparency log each qualify.

litespec/ext/audit#anchor-the-audit-chain-outside-its-own-store

Verify the integrity chain on a schedule#

ruleguidance

Requirement An audit deployment MUST verify its integrity chain on a recurring schedule and raise an alert on a break.

Rationale Unverified tamper evidence proves nothing. The verification result is the evidence a reviewer receives.

litespec/ext/audit#verify-the-integrity-chain-on-a-schedule

Add a correcting record#

ruleguidance

Requirement A correction to the trail MUST be a new record that references the earlier one.

Rationale An edited history carries no evidential value. The correction and the original both stay visible.

litespec/ext/audit#add-a-correcting-record

Retain each category for its declared period#

ruleguidance

Requirement An audit deployment MUST delete a record after the retention period declared for its category.

Rationale An indefinite period is the hardest retention to defend and the easiest to correct at design time.

litespec/ext/audit#retain-each-category-for-its-declared-period

Remove the identity mapping on erasure#

ruleguidance

Requirement An erasure request MUST remove the mapping from actor identifier to person and retain every audit record.

Rationale The record keeps its evidential value. The person stops being identifiable from it. This depends on the record holding no personal data.

litespec/ext/audit#remove-the-identity-mapping-on-erasure

Give the trail a read path#

ruleguidance

Requirement An audited system MUST provide a trail read as a use case needing a Permission record, filtered by tenant, actor, action, target, and outcome.

Rationale A trail that no use case reads is storage rather than a control. The actor holds a Role at a scope. The Role allows the Permission. The guard permits the read.

litespec/ext/audit#give-the-trail-a-read-path

Audit reads of the trail#

ruleguidance

Requirement A read of the audit trail MUST itself produce an audit record.

Rationale Access to the evidence is a sensitive action. An unrecorded read leaves the reviewer outside the trail.

litespec/ext/audit#audit-reads-of-the-trail

Record personal-data reads#

ruleguidance

Requirement A query returning personal data across a boundary MUST produce an audit record, defining the account boundary in the adoption decision where tenancy is absent.

Rationale A read leaves no state change and therefore no other trace. Disclosure without a record cannot be investigated.

litespec/ext/audit#record-personal-data-reads

Record a bulk read inside its own boundary#

ruleguidance

Requirement A query returning personal data for more subjects than its declared threshold MUST produce an audit record, inside one tenant or account boundary as well.

Rationale The cross-boundary rule catches a disclosure to the wrong party. It does not catch an export of every record a party may read one at a time. That is the shape a copied dataset has. Article 30 of the GDPR asks what categories of data were processed and by whom. An unrecorded bulk read cannot answer either.

Example A project records the threshold in the adoption decision. A list endpoint whose page size can exceed it audits the read with the subject count rather than the subjects.

litespec/ext/audit#record-a-bulk-read-inside-its-own-boundary

Record bulk export#

ruleguidance

Requirement An export that returns a set of records to a caller MUST produce an audit record naming the requested scope.

Rationale Bulk extraction is the highest-consequence read in most products. The scope states what left the system.

litespec/ext/audit#record-bulk-export

Reject an undeclared Command#

ruleguidance

Requirement An automated check MUST fail when a Command declares neither audited nor excluded status, with a composition refusal at startup satisfying this rule.

Rationale The next Command is written by someone who did not read this page. Only an automated gate catches it.

litespec/ext/audit#reject-an-undeclared-command

Conventions#

Keep audit infrastructure in one location#

conventionguidance

Default Keep the audit record, its store, the producer, and the integrity job under Infrastructure/Auditing/.

Replacement A consumer records a departure from this default as an Exception record with reason and review date.

Rationale One Infrastructure location contains the evidence mechanism.

litespec/ext/audit#keep-audit-infrastructure-in-one-location

Declare audit selection beside the Command#

conventionguidance

Default Declare the action, target kind, category, and exclusion reason as an attribute on the Command type.

Replacement A consumer records a departure from this default as an Exception record with reason and review date.

Rationale The declaration stays with the code it describes. A review reads both at once.

litespec/ext/audit#declare-audit-selection-beside-the-command

Use a fixed category vocabulary#

conventionguidance

Default Classify each audited action as authentication, authorization-denial, grant-change, monetary-action, tenant-configuration, personal-data-read, bulk-export, or background-process-change.

Replacement A consumer records a departure from this default as an Exception record with reason and review date.

Rationale The category drives retention and the review query. A free-text value defeats both.

litespec/ext/audit#use-a-fixed-category-vocabulary

Store the trail beside the business data#

conventionguidance

Default Store audit records in the business database, under a separate account holding INSERT and SELECT privileges.

Replacement A consumer records a departure from this default as an Exception record with reason and review date.

Rationale One database provides the atomic success write. Privilege separation provides the append-only guarantee.

litespec/ext/audit#store-the-trail-beside-the-business-data

Publish a copy to the security platform#

conventionguidance

Default Publish a copy of each record to an external security platform through the outbox, and keep the local store authoritative.

Replacement A consumer records a departure from this default as an Exception record with reason and review date.

Rationale Delivery is at-least-once. Evidence needs one durable source. The copy serves detection. The store serves accountability.

litespec/ext/audit#publish-a-copy-to-the-security-platform

Dependencies#

This extension requires no additional baseline package. The tenancy extension owns tenant resolution when a deployment serves several customer organizations. The outbox extension owns delivery when litespec/ext/audit#publish-a-copy-to-the-security-platform applies.

Build an audit trail states one compliant implementation on the command pipeline of the dotnet stack.

Verification#

IDMarkEvidence
litespec/ext/audit#record-the-audit-adoption-decisionguidanceDoes the adoption decision record the required fields? Is no record approved behind its draft?
litespec/ext/audit#declare-audit-selection-on-every-commandguidanceDoes every Command declare audited status or excluded status with a reason?
litespec/ext/audit#cover-the-required-audit-categoriesguidanceDoes the declared selection contain one entry for each required category?
litespec/ext/audit#exclude-indiscriminate-auditingguidanceDoes the selection justify each audited category rather than auditing every Command?
litespec/ext/audit#emit-audit-records-at-the-mediation-boundaryguidanceDoes the producer run in the mediation pipeline rather than inside handlers?
litespec/ext/audit#restrict-handler-contribution-to-determined-valuesguidanceDoes each handler contribute only the audit values it alone determines?
litespec/ext/audit#exclude-derived-audit-trailsguidanceIs the trail produced directly rather than derived from events, triggers, or capture?
litespec/ext/audit#record-the-required-audit-fieldsguidanceDoes every record carry each required field, including the tenant value?
litespec/ext/audit#state-the-action-as-use-case-identityguidanceDoes each action value name the invoked use case?
litespec/ext/audit#record-the-failure-code-on-an-unsuccessful-attemptguidanceDoes each denied or failed record carry its stable failure code?
litespec/ext/audit#version-the-audit-record-shapeguidanceDoes every record carry an explicit schema version?
litespec/ext/audit#use-a-closed-actor-setguidanceIs every actor one of the five Actor kinds, with devices and processes mapped?
litespec/ext/audit#record-delegated-administrative-accessguidanceDoes an action under a support grant record that the grant was used?
litespec/ext/audit#record-every-attempt-outcomeguidanceDoes every record carry one of the three declared outcomes?
litespec/ext/audit#record-refused-authorizationguidanceDoes a refused authorization produce a denied record?
litespec/ext/audit#record-failed-audited-attemptsguidanceDoes a permitted attempt that throws produce a failed record?
litespec/ext/audit#record-the-request-originguidanceDoes each request-produced record carry origin, client, and request identifier?
litespec/ext/audit#record-the-operation-trace-identityguidanceDoes each record carry the trace identifier of its operation?
litespec/ext/audit#exclude-secrets-and-payment-dataguidanceIs every record free of credentials, tokens, session identifiers, keys, and payment values?
litespec/ext/audit#exclude-personal-data-beyond-actor-identityguidanceDoes each record hold only pseudonymous actor and target identifiers?
litespec/ext/audit#exclude-state-snapshotsguidanceIs every record free of before and after state copies?
litespec/ext/audit#commit-a-success-record-with-its-business-changeguidanceDoes a succeeded record commit in the same transaction as its change?
litespec/ext/audit#write-an-unsuccessful-record-outside-the-failed-transactionguidanceDoes a denied or failed record survive the rollback of its transaction?
litespec/ext/audit#stage-the-audit-write-before-the-business-commitguidanceDoes the audit stage run before the business commit stage?
litespec/ext/audit#publish-unsuccessful-write-failuresguidanceDoes the deployment publish the write failure count of the unsuccessful path?
litespec/ext/audit#scope-every-record-to-its-tenantguidanceDoes each record carry the tenant of the acted-on resource?
litespec/ext/audit#enforce-append-only-through-storage-privilegeguidanceDoes the writing account lack update and delete privileges on the store?
litespec/ext/audit#provide-tamper-evidenceguidanceDoes each record chain to its predecessor with the stated hash?
litespec/ext/audit#anchor-the-audit-chain-outside-its-own-storeguidanceDoes a checkpoint the operators cannot rewrite anchor the chain head?
litespec/ext/audit#verify-the-integrity-chain-on-a-scheduleguidanceDoes a scheduled job verify the chain and alert on a break?
litespec/ext/audit#add-a-correcting-recordguidanceIs each correction a new record referencing the earlier one?
litespec/ext/audit#retain-each-category-for-its-declared-periodguidanceAre records deleted after the declared retention of their category?
litespec/ext/audit#remove-the-identity-mapping-on-erasureguidanceDoes erasure remove the identity mapping while retaining the records?
litespec/ext/audit#give-the-trail-a-read-pathguidanceDoes a permission-gated read filter the trail by every declared filter?
litespec/ext/audit#audit-reads-of-the-trailguidanceDoes a completed trail read produce one audit record?
litespec/ext/audit#record-personal-data-readsguidanceDoes a cross-boundary personal-data query produce a record?
litespec/ext/audit#record-a-bulk-read-inside-its-own-boundaryguidanceDoes a query above the threshold produce a record carrying the subject count?
litespec/ext/audit#record-bulk-exportguidanceDoes each export produce a record naming the requested scope?
litespec/ext/audit#reject-an-undeclared-commandguidanceDoes the build or startup fail for a Command with no declaration?
litespec/ext/audit#keep-audit-infrastructure-in-one-locationguidanceDoes the audit code use the documented path or a recorded Exception?
litespec/ext/audit#declare-audit-selection-beside-the-commandguidanceDoes each Command carry the declaration attribute or a recorded Exception?
litespec/ext/audit#use-a-fixed-category-vocabularyguidanceDoes each category value belong to the eight-value vocabulary?
litespec/ext/audit#store-the-trail-beside-the-business-dataguidanceIs the trail in the business database under separated privileges or a recorded Exception?
litespec/ext/audit#publish-a-copy-to-the-security-platformguidanceDoes a published copy leave the local store authoritative, or is there a recorded Exception?