In this volume · VOLUME 08
System Features
Notifications Audit Logs

Chapter 8.2 — Audit Logs

1. Purpose

Audit logs are the platform's tamper-resistant record of "who did what, when, from where". This chapter defines the audit log model — events captured, structure, retention, query patterns, security controls — that lets auditors, security teams, and partner admins reconstruct any state in the platform's history.

2. Why it matters

  • Audit attestation — external auditors test the audit trail; weak trails cost audit scope.
  • Security forensics — when something goes wrong (or is suspected to), the trail is how it gets investigated.
  • Compliance — SOC 2 CC4, ISO 27001 A.12.4, PCI DSS Req. 10 all require detailed activity logging.
  • Disputes — "I didn't do that" needs an authoritative answer.

3. Scope of logging

travoBooks logs every business-meaningful event, not raw HTTP requests. This is a deliberate choice — HTTP-level logs go to general observability (Chapter 8.x), but audit logs capture intent.

3.1 Events logged

Categories: - Authentication: login success / failure, MFA challenge, password change, logout, session revoke, token issued / revoked. - Authorization: permission grant / revoke, role assignment / removal. - Customer master: create, update, credit-limit change, sanctions screening result, deactivate. - Supplier master: create, update, bank-detail change (extra-sensitive), commission-rule change. - Booking: each state transition (DRAFT → HELD → ISSUED → ...), price quote, supplier call success/failure. - Ticketing: issuance, void, refund, exchange. - Memos: receipt, dispute, settlement. - Financial: every JE posted (header + summary; lines accessible via JE id), invoice generated, payment received, payout sent. - Reconciliation: every recon run, match decision, exception resolution. - Tax: rule change, return generation, payment. - Period: soft-close, close, reopen, lock. - System config: partner config changes, COA changes, role definition changes. - Imports / Exports: file upload, file commit, report generation, report download. - API: PAT created / revoked, webhook subscription created / updated.

3.2 Events NOT in audit log

These go to general observability logs, not audit logs: - Search queries (operational; high volume) - Read-only views (high volume; "who looked at X" is a Phase 2 PII-access log) - Cache hits / misses - Internal worker heartbeats

A separate PII access log (Phase 2) covers "who viewed sensitive customer data" — distinct from audit logs which cover writes / decisions.

4. Schema

audit_logs (
  audit_log_id          BIGINT PK AUTO_INCREMENT,
  partner_id            BIGINT NOT NULL,
  event_type            VARCHAR(100) NOT NULL,    -- 'booking.issued', 'customer.credit_limit_changed'
  event_severity        ENUM('INFO','NOTICE','WARN','ALERT'),
  actor_type            ENUM('user','system','api_token','external'),
  actor_id              BIGINT NULL,
  actor_display_name    VARCHAR(255),
  target_entity_type    VARCHAR(50) NULL,
  target_entity_id      BIGINT NULL,
  request_id            CHAR(26) NULL,
  ip_address            VARBINARY(16) NULL,
  user_agent            VARCHAR(500) NULL,
  geolocation           VARCHAR(100) NULL,
  event_payload         JSON NOT NULL,
  diff                  JSON NULL,                -- before/after for updates
  reason                VARCHAR(500) NULL,        -- user-supplied reason for sensitive ops
  parent_audit_log_id   BIGINT NULL,              -- nested actions
  created_at            DATETIME(6) NOT NULL,
  hash_chain_prev       CHAR(64),                 -- previous row's hash
  hash_chain_self       CHAR(64),                 -- this row's hash
  KEY idx_partner_time (partner_id, created_at),
  KEY idx_event_type (event_type),
  KEY idx_actor (actor_type, actor_id),
  KEY idx_target (target_entity_type, target_entity_id),
  KEY idx_request (request_id)
) ENGINE=InnoDB;

5. Tamper-evidence: hash chaining

Each audit row's hash_chain_self = SHA256(prev_hash || canonical(row_without_chain_fields)). The chain anchors at a periodically-signed root hash: - Every 1 hour, the latest hash is signed with the platform's audit-attestation private key. - The signature is published to an append-only attestation log. - An attacker who alters a past row breaks every subsequent hash, detected by chain verification.

This does not make rows immutable; it makes tampering detectable. Combined with DB row-level access control (audit logs are write-once via stored-procedure interface), the practical guarantee is strong.

6. Common event payloads

booking.issued

{
  "booking_id": 1009287,
  "booking_ref": "TVB-2026-000123",
  "customer_id": 4521,
  "supplier_id": 17,
  "amount": "65400.00",
  "currency": "BDT",
  "primary_ticket_number": "176-2400000123",
  "service_date_start": "2026-05-28"
}

customer.credit_limit_changed

{
  "customer_id": 4521,
  "before": {"credit_limit": "500000.00"},
  "after": {"credit_limit": "750000.00"},
  "reason": "Q2 increase approved; ref MEMO-2026-Q2-CL-018"
}

supplier.bank_details_changed

{
  "supplier_id": 17,
  "before": {"account_number_masked": "****1234", "bank_swift": "ABCDXXXX"},
  "after": {"account_number_masked": "****5678", "bank_swift": "EFGHXXXX"},
  "reason": "Supplier-confirmed change",
  "vendor_fraud_hold_until": "2026-05-26T19:15:00+06:00"
}

period.closed

{
  "period_year": 2026,
  "period_month": 5,
  "trial_balance_balanced": true,
  "snapshot_path": "s3://travobooks-snapshots/partner-42/2026-05/tb.json",
  "initiator_user_id": 18,
  "approver_user_id": 7
}

7. Reason capture

For sensitive operations, the platform requires a free-text reason field: - Credit-limit change above threshold - Supplier bank-detail change - Refund without standard quote - Period reopen - Permission grant for sensitive role - Manual journal entry

This is stored both in the audit log and in the underlying entity history. The UI prompts the user; the API requires it in the request payload.

8. Query patterns

Who changed customer credit limit recently?

SELECT * FROM audit_logs
WHERE event_type = 'customer.credit_limit_changed'
  AND target_entity_type = 'customer'
  AND target_entity_id = 4521
  AND created_at >= NOW() - INTERVAL 90 DAY
ORDER BY created_at DESC;

Reconstruct user's session activity

SELECT * FROM audit_logs
WHERE actor_type = 'user' AND actor_id = 18
  AND created_at BETWEEN '2026-05-25' AND '2026-05-26'
ORDER BY created_at;

All entity changes from a single request

SELECT * FROM audit_logs
WHERE request_id = '01HZ7P8X3R5...'
ORDER BY created_at;

Period audit pack

SELECT * FROM audit_logs
WHERE partner_id = 42
  AND created_at BETWEEN '2026-05-01' AND '2026-05-31 23:59:59.999999'
ORDER BY created_at;

9. UI surfacing

View Purpose
Entity history On every business entity (customer, booking, JE), a "History" tab shows recent audit events for that entity
User activity Per-user dashboard — recent actions, sessions, IPs
System events Partner-admin view — recent admin/configuration changes
Search audit log Filter by event-type, actor, target, time range

Access controlled by audit.read.partner permission (limited audience).

10. Retention

Retention class Period
Financial-impact events 10 years (or local statute)
Other business events 7 years
Authentication events 7 years

After in-DB retention period: archived to S3-Glacier with full chain attestation; restorable on demand for legal hold.

11. Privacy

Audit logs may contain PII (e.g., a customer creation event includes the customer's data). Treatment: - Subject to the same data-protection controls as primary data (Chapter 12.5). - Right-to-erasure honoured by anonymising the event_payload while preserving the audit-log row itself (regulatory exemption — audit trails are commonly excluded from erasure). - Access logged (meta-audit) — viewing the audit log is itself audited.

12. Performance

Audit-log writes are on the hot path of every business operation; the platform optimises: - Write path: append-only, no triggers, single-table insert with prepared statement. - Indexes tuned for the query patterns above. - High-volume events (e.g., per-segment status updates) batched into a single audit row with an array payload. - Older partitions moved to compressed cold tablespace (Phase 4 tiered storage).

13. Anomaly detection (Phase 2)

ML-based anomaly detection runs over the audit stream: - Bursts of failed logins → trigger account-level rate limit. - Off-hours bank-detail changes → escalation. - New IP for sensitive operations → step-up auth. - Permission changes for ex-employees → alert.

14. Common pitfalls

  • ⚠️ Logging high-volume read events into the audit log. Use the access log instead; otherwise the audit log becomes unsearchable.
  • ⚠️ Putting raw passwords or tokens in payloads. Strip before logging.
  • ⚠️ Updating audit log rows after creation. Don't. The chain detects it.
  • ⚠️ Deleting old audit rows for storage. Use the archival path; never DELETE directly.
  • ⚠️ Skipping reason capture. Without reasons, audit becomes "who" without "why" — only half the story.
  • 🔒 Audit-log access without itself being audited. Meta-audit must be on.