Defines append-only audit recording with a hash chain or WORM store, required actor-object-reason fields, a store separate from the application database, and retention that outlives ordinary erasure except where a lawful exception is documented.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Security-relevant actions write an append-only audit record to a store that is not the application OLTP database. Each record names the actor, the object, and a machine-readable reason. Integrity is provided by a per-record hash chain, by a WORM lock, or by both. Retention follows a named class. Ordinary right-to-erasure jobs do not delete records that are still inside that class unless a documented lawful exception applies.
Scope
- Authorization changes, break-glass sessions, data exports, and other control-plane actions listed in
event-catalog.yaml. - Writer APIs, chain verification jobs, legal-hold flags, and the boundary with erasure workflows.
- Clock source and digest algorithm used for chain verification.
Outside this block
- Product analytics, debug logs, or request traces that are not security evidence.
- Choosing a specific vendor WORM product beyond the adoption profile.
- Full erasure orchestration, which lives in the related privacy fulfillment block.
Contract
- For event classes explicitly marked
fail_closed: truein the catalog, the audit writer refuses to complete the action unless actor identifier, object identifier, and reason code are present. Other events follow their declared availability policy rather than a global mutation ban. - Stored records cannot be updated or deleted by application credentials; only append is allowed.
- Each record stores
record_sha256over canonical bytes andprev_record_sha256; a verification job fails closed on a break. - Application database restore, truncate, or row delete does not remove corresponding audit records.
- Retention class and
keep_daysare stored on the record; erasure jobs skip those rows and emit a pointer to the class. - A legal hold identifier extends retention past
keep_daysuntil the hold is released by a named owner.
Implementation guidance
- Give application servers append-only credentials to a separate audit cluster.
- Canonicalize bytes before hashing; unknown catalog names fail closed.
- Reason codes are required; free-text is optional commentary.
Failure handling and safeguards
- If the audit store is down, only catalog events explicitly classified as high-integrity and
fail_closed: trueblock the associated action; other events use their documented queue or degradation path. - Chain breaks freeze the suspect writer and page security.
- Erasure workers without a retention-class mapping must not delete audit rows.
Verification and operations
- Verify the last
verify_batch_sizerecords nightly against the head digest. - After OLTP restore, prove audit still covers that window.
- Track erasure skips and review legal-hold owners quarterly.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.