ADR-0007: Two complementary audit systems — triggers and business log
Status
AcceptedTags
audit, compliance, postgresql, triggers, logging, leaveDecision
Run two separate audit systems in parallel, each answering a different question:| System | Table | Mechanism | Question answered |
|---|---|---|---|
AuditTrail | audit_trails | PostgreSQL AFTER triggers (automatic) | “What changed in this row?” |
AuditLog | audit_log | Manual service calls | ”Who did what and why?” |
Why
A single system cannot serve both purposes efficiently:- Triggers capture complete before/after row snapshots automatically — zero application code needed, atomic with the write transaction. But they have no business context: no user name, no reason, no cross-entity narrative.
- Business-level logging captures rich context (user name, IP, user agent, reason, cross-entity correlation) but requires explicit integration at every call site.
When to use which
UseAuditTrail (triggers) for:
- Data recovery (“what was the field value before?”)
- Compliance proof of row changes
- Debugging (“when did this record change?”)
- Zero-effort automatic tracking
AuditLog (manual) for:
- Security auditing (“who accessed what from where?”)
- Business reporting (“approvals per manager in Q4”)
- Failed attempt logging
- Non-CRUD actions (exports, logins, permission checks)
- Cross-entity narratives (“approval affected both request and balance”)
Rules for agents
- Never replace trigger-based audit with application code writes to
audit_trails— triggers handle it automatically AuditLogmust be called explicitly in use cases for business actions (approve, reject, export, etc.); it does not fire automatically- In approval/rejection flows, call BOTH: let the trigger capture the row change, then call
auditService.CreateAuditLog(...)for the business context audit_trailsis append-only — never UPDATE or DELETE rowsaudit_logentries must include:UserID,UserName,UserRole,Action,EntityType,EntityID