Deepanshu's Diary

Using Kafka-Style Event Streams as the System of Record for Auditable Workflows

--system-designeventsauditability

Friday, 5:42pm — an operator gets an escalated compliance request: a customer says they never consented to a recorded call. The audio is missing from the archive. How do we prove what happened, in what order, and who approved the call?

This tension — the need to turn ephemeral runtime activity into an immutable, replayable audit trail — is why I favor a Kafka-style event streaming backbone for auditability. In this post I sketch a clear mental model, walk through an architecture example (voice agent audit + WhatsApp campaign queue), explain what I implemented in my portfolio, show where this approach breaks, and give a practical checklist for production readiness.

Mental model: the audit log is the system of record

Think of a Kafka-like topic as a time-ordered, append-only ledger. Every significant state change or decision becomes an event: "call-started", "speech-transcript", "consent-granted", "operator-approved-send". Events are immutable, tagged with timestamps, producer ids, and opaque offsets. Consumers (analytics, compliance UI, replay workers) read the stream and reconstruct the state by applying events in order.

Key properties this model buys you:

- Immutability and order: once appended, events aren’t mutated in place; the stream preserves the temporal sequence needed for audits. - Replayability: you can reprocess from offset X to rehearse an investigation or re-run enrichment logic. - Decoupling: producers don't need to know every downstream consumer; they emit and forget. - Retention & compaction: you control how long raw events are kept and when to compact to a compacted state for long-term storage.

Architecture / data-flow example

Below is a simplified flow for two scenarios: a voice agent producing audit events, and a WhatsApp campaign queue enforcing consent and review gates.

1) Voice Agent audit pipeline

- Voice runtime (producer): emits events to topic voice-audit: call-started, detected-language, transcript-chunk, consent-flag, operator-action. - Enrichment service (consumer/producer): reads transcript chunks, attaches speaker IDs, redacts PII, writes enriched events to voice-audit-enriched. - Approval service (consumer): watches consent and operator-action events to decide if call recording may be stored or forwarded to long-term archive. - Operator UI (consumer): materializes event streams into a timeline for investigators. - Audit DB / cold archive: periodic snapshots or compacted state are exported for long-term retention and legal holds.

2) WhatsApp campaign queue (lead -> review -> approved-send)

- Scraper (producer): emits lead-found events to leads topic with lead id, selectors, consent metadata. - Consent-checker (consumer/producer): inspects lead, emits consent-verified or consent-missing events. - Review queue (topic or compacted table): events that need human review are enqueued; operator approves by producing operator-approved-send event. - Send-worker (consumer): gated by operator-approved-send; performs an approved-send action (note: in my portfolio this final send remains a gated action — see What I built).

A sketch of an event envelope (conceptual)

| field | purpose | |---|---| | event_id | unique UUID for idempotency | | producer | runtime or service name | | event_type | e.g., consent-granted | | timestamp | producer time (and server receipt time saved by broker) | | payload_schema | schema version reference | | payload | domain data |

Why schema and envelope matter: a schema registry + small envelope makes evolution and validation tractable across teams [1].

What I built around this idea

I implemented multiple components that use this pattern across projects in my portfolio. Notably:

- Amazon Voice Agent: provider-agnostic mock-first voice runtime that emits audit events, supports multilingual detection, consented memory, approval-gated calls, encrypted provider keys, health probes, and a full operator UI. Audit events and the approval gates are implemented and wired to the operator UI; live streaming, barge-in, and real provider acceptance are still operational gates. (Evidence: my project artifacts and operator UI in the Voice Agent work.)

- Google WhatsApp Scraper & Growth Engine: a scraper pipeline produces leads and consent metadata, a Growth Engine UI surfaces review/qualification and stores local review-packs. The system enqueues leads for review and emits approval events; sending messages remains a gated "approved-send" that has not been exercised against provider APIs. The campaign queue and review path are implemented; outbound send is intentionally review-only at present.

- ARIL monorepo: tenant-scoped knowledge bases, document/chunk embedding pipelines and protected routes — used as examples of tenant isolation and eventing patterns for multi-tenant audit trails. Real production Postgres migrations remain an environment gate.

I’ve kept these systems mock-first and review-gated so the audit trail is testable and observable without releasing live actions into external systems.

Where this breaks (failure modes and mitigations)

1) Duplicate or out-of-order events (producer retries, network blips) - Symptom: audit shows two "consent-granted" events or inconsistent state. - Mitigation: event_id idempotency keys, deduplication at consumer side, and use of producer-side transactional writes/exactly-once semantics where available [1].

2) Schema drift and consumer breakage - Symptom: new event payloads cause consumer deserialization failures. - Mitigation: schema registry with backward/forward compatibility rules; consumer-side feature toggles and robust validation.

3) Retention/compaction policy accidentally deletes evidence - Symptom: required legal evidence falls outside retention window. - Mitigation: legal-hold tagging, tiered storage (hot topic -> cold archive), periodic snapshots exported to immutable storage.

4) PII leakage and unauthorized access - Symptom: sensitive transcript content exposed to non-authorized consumers. - Mitigation: field-level encryption/redaction before emission, broker ACLs, encryption-at-rest, and strict consumer IAM.

5) Broker or partition hotspotting - Symptom: single partition overloaded causing latency or ordering delays. - Mitigation: partitioning keys chosen for load balance, backpressure, and autoscaling brokers.

A practical checklist (5–8 actionable checks)

- Enforce event envelopes with an event_id, producer id, and schema_version. - Use a schema registry and automated compatibility checks for all producers. - Implement idempotency and consumer-side dedup where side effects occur. - Protect PII: redact/encrypt at source, never rely only on transport encryption. - Define retention policies and export immutable snapshots for legal-hold scenarios. - Monitor consumer lag, partition throughput, broker health, and set alerts for unusual patterns. - Gate outbound actions with human approvals recorded as events.

Conclusion

Kafka-style event streaming gives you the primitives audits need: an ordered, append-only record that you can replay, inspect, and govern. The key is combining these primitives with schema governance, idempotency, PII controls, and operational practices (retention, snapshots, monitoring). In my work on a voice agent and a WhatsApp campaign flow I use these patterns to make decisions and approvals auditable while keeping live actions gated until we’ve satisfied safety and legal checks.

References

[1] Apache Kafka documentation: https://kafka.apache.org/documentation/

[2] Model Context Protocol introduction: https://modelcontextprotocol.io/introduction

[3] "On the difficulty of time in distributed systems" (arXiv:2005.11401): https://arxiv.org/abs/2005.11401

[4] Related distributed systems / audit literature (arXiv:2307.03172): https://arxiv.org/abs/2307.03172

[5] My public portfolio and project repos (examples referenced above): https://github.com/deepanshuvermaa/my-portfolio

[6] Related project artifacts and experiments: https://github.com/deepanshuvermaa/museum-of-failure, https://github.com/deepanshuvermaa/go2-payroll

Copied!
Back to all posts