Audit Service — Sync Contract
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template
1. Sync contract overview
The Audit Service is an append-only event consumer. It does not participate in bidirectional sync, offline-first patterns, or conflict resolution. This document records the conflict policy per aggregate for completeness and explicitly states why standard sync primitives do not apply.
2. Per-aggregate conflict policy
| Aggregate | Conflict policy | Rationale |
|---|---|---|
AuditEntry | append_only | Write-once; immutable by design; no updates, no deletes, no conflict possible |
AuditExport | server_authoritative | Export job lifecycle (queued → processing → completed/failed) is managed exclusively by server-side worker; no client-side mutation permitted |
3. Offline / sync scope
The Audit Service does not participate in the platform's offline-first sync protocol described in docs/16-offline-first-and-sync.md.
| Scenario | Behavior |
|---|---|
| Client offline, audit query not cached | Queries fail gracefully; no offline cache supported (compliance data must be live) |
| NATS partition / reconnect | At-least-once delivery via JetStream; deduplication on source_event_id handles duplicates on reconnect |
| Multi-region | Primary region is authoritative; async replica for DR; no active-active write conflict possible (append-only) |
4. Idempotency guarantees
| Operation | Idempotency mechanism |
|---|---|
| Event ingestion | source_event_id UNIQUE index; INSERT ... ON CONFLICT DO NOTHING |
| Export job creation | Each export has a unique AuditExportId (ULID); creating a second export with identical filters creates a new job (not idempotent by design) |
| Chain-hash computation | Deterministic given the same input fields; recomputing the same entry always yields the same hash |
5. At-least-once delivery contract
All event producers use NATS JetStream with AckPolicy=Explicit. The audit service ACKs each message only after successful Postgres INSERT. If the INSERT fails (DB unavailable), the message is NAK'd and redelivered. Combined with source_event_id dedup, this guarantees at-least-once, effectively-exactly-once ingestion per event.