Summary
With AUDIT_ARCHIVE_ON_EXPIRY=false, archiveExpired() permanently deletes audit records. Direct DB access can also DELETE FROM audit_chain. No triggers or constraints prevent this.
Fix
- Remove the non-archive deletion path — always archive
- Add PostgreSQL trigger preventing DELETE on audit_chain (only allow INSERT into cold_audit + DELETE in same transaction)
- Consider append-only table design
Summary
With AUDIT_ARCHIVE_ON_EXPIRY=false, archiveExpired() permanently deletes audit records. Direct DB access can also DELETE FROM audit_chain. No triggers or constraints prevent this.
Fix