Skip to content

12 โ€” Audit Trails (SOX/GDPR) & Observability

JD: "Verify audit log integrity, including immutable log structure, correlation ID chains, and SOX-compliant event emission." Plus nice-to-haves: SOX/GDPR audit-trail validation and Azure Monitor / Application Insights. In a financial platform this is a hard requirement, not a footnote.


1. Why audit logs matter here

In plain words: in regulated finance, you must be able to prove after the fact exactly what happened โ€” who did what, when, on which record, and whether it was approved. Auditors (for SOX) require that these records exist, are complete, and cannot be tampered with. If an agent released a payment, there must be an unforgeable trail.

SOX (Sarbanes-Oxley) = US law requiring accurate financial records and controls; drives the "immutable, complete, attributable" audit-log requirement. GDPR = EU privacy law; drives data-minimisation, consent, right-to-erasure, and "don't log PII you don't need."


2. What an audit event should contain (and how you'd test it)

A well-formed audit event (validate as a schema, file 03):

{
  "event_id": "uuid",
  "event_type": "PAYMENT_RELEASED",
  "timestamp": "2026-07-07T10:00:00Z",     // UTC, monotonic
  "actor": "user:qa_approver",             // WHO (human or agent/service identity)
  "entity_type": "payment",
  "entity_id": "42",                       // WHAT record
  "action": "release",
  "before": {"status": "pending"},         // state change captured
  "after":  {"status": "released"},
  "correlation_id": "req-abc-123",         // links the whole request chain
  "trace_id": "langfuse-trace-xyz",        // links to the agent trace (file 09)
  "outcome": "success",
  "prev_hash": "โ€ฆ", "hash": "โ€ฆ"            // tamper-evidence (see ยง4)
}

Test coverage: - Presence โ€” every audited action emits exactly one event (no missing, no duplicate). - Schema โ€” required fields present, types/formats valid (Pydantic/JSON Schema). - Attribution โ€” actor is correct (the approver, or the agent's service identity). - State capture โ€” before/after match the actual DB transition (cross-check file 05). - Timestamp โ€” UTC, plausible, ordered.


3. Correlation-ID chains (the JD phrase)

In plain words: a correlation ID is one identifier threaded through every service, log line, agent step, and audit event for a single request. It lets you reconstruct the entire journey of one transaction across the API, the agent graph, the DB, and the audit log.

How to test:

def test_correlation_chain(client, db, langfuse):
    r = client.post("/payments/42/release", headers={"X-Correlation-ID": "req-abc-123"})
    cid = r.headers["X-Correlation-ID"]
    assert cid == "req-abc-123"                          # propagated back

    # same id appears across every layer
    assert db.audit_events(entity_id=42)[0].correlation_id == cid
    assert langfuse.get_trace_by_tag(cid) is not None    # agent trace linked
    assert all(log.correlation_id == cid for log in app_logs_for(cid))  # app logs
Interview line: "I test that a single correlation ID propagates end-to-end โ€” request header โ†’ app logs โ†’ agent trace โ†’ DB rows โ†’ audit event โ€” so any transaction is fully reconstructable. A broken chain is itself a defect in a SOX context."


4. Immutable / tamper-evident log structure

In plain words: "immutable" means once written, an audit record can't be silently changed or deleted. Common techniques: append-only storage, WORM (write-once-read-many), and hash-chaining (each record stores a hash of the previous record, like a mini blockchain) so any edit breaks the chain.

How to verify integrity:

def test_audit_log_is_tamper_evident(audit_log):
    events = audit_log.read_all()
    # 1. hash chain intact: each event's prev_hash == hash of the prior event
    for prev, cur in zip(events, events[1:]):
        assert cur.prev_hash == prev.hash
        assert cur.hash == sha256(canonical(cur.body) + cur.prev_hash)
    # 2. no gaps in sequence / no out-of-order timestamps
    assert [e.seq for e in events] == list(range(events[0].seq, events[-1].seq + 1))
    # 3. append-only: attempting an update/delete is rejected
    with pytest.raises(PermissionError):
        audit_log.update(events[0].event_id, {"actor": "someone_else"})
Also test: retention (records kept for the required period), access controls (only authorised roles can read), and that a tampered record is detectable (flip a byte โ†’ hash check fails).


5. SOX-compliant event emission

Things SOX-oriented tests assert: - Completeness โ€” every financially-material action (create/approve/modify/release/decline) emits an event; nothing silent. - Segregation of duties โ€” the actor who initiated isn't the one who approved (assert distinct actors on the two events). - Approval before action โ€” a RELEASE event must be preceded by an APPROVAL_GRANTED event for high-value actions (ties to the agent path, file 08). - Non-repudiation โ€” actor identity is authenticated and recorded; can't be forged. - No sensitive data leakage in logs (GDPR overlap).


6. GDPR angles (nice-to-have)

  • Data minimisation โ€” don't log full PII (mask/tokenise PANs, emails). Test that logs contain no raw PII.
  • Right to erasure โ€” a deletion request removes/anonymises personal data while keeping the audit trail's integrity (often: erase PII fields, keep the hash-chained event). Test both.
  • Consent / lawful basis flags present where required.
  • Data residency โ€” data stored in the correct region.

7. Azure Monitor / Application Insights (observability-assisted debugging)

In plain words: Azure's observability stack. Application Insights collects telemetry (requests, dependencies, exceptions, custom events, traces) from the app; Azure Monitor / Log Analytics stores and queries it with KQL (Kusto Query Language); Alerts fire on conditions.

For a QA engineer: - Failure triage โ€” when an E2E/agent test fails in CI, pull the correlated telemetry by correlation ID to see the exception/dependency that broke. Pairs with Langfuse (app-level vs LLM-level view). - Distributed tracing โ€” App Insights correlates requests across services (same idea as the correlation ID). - KQL sketch:

requests
| where customDimensions.correlation_id == "req-abc-123"
| project timestamp, name, resultCode, duration
| order by timestamp asc
- Custom metrics / alerts โ€” emit test-relevant metrics (agent error rate, refusal rate, latency) and alert on regressions; can feed a release gate.

Interview line: "I use Application Insights to debug failures by correlation ID โ€” reconstructing the request across services with KQL โ€” and to watch production signals like agent error/latency/refusal rates, which double as inputs to my quality gates and drift detection."


Rapid-fire recall

  • Audit event = who/what/when/before-after/correlation_id/trace_id/outcome + tamper-evidence.
  • Correlation ID threads request โ†’ logs โ†’ agent trace โ†’ DB โ†’ audit; test it end-to-end.
  • Immutable = append-only/WORM/hash-chain; test chain intact + edits rejected + gaps detected.
  • SOX: completeness, segregation of duties, approval-before-action, non-repudiation.
  • GDPR: no PII in logs, erasure keeps audit integrity, residency.
  • App Insights + KQL by correlation ID for failure triage; metrics/alerts feed gates.