Skip to content

feat(ledger): write audit_events row per ledger write - #135

Merged
tiana-code merged 3 commits into
mainfrom
feat/E-01-audit-write-path
Jun 15, 2026
Merged

feat(ledger): write audit_events row per ledger write#135
tiana-code merged 3 commits into
mainfrom
feat/E-01-audit-write-path

Conversation

@tiana-code

Copy link
Copy Markdown
Owner

Writes one immutable platform.audit_events row inside the business transaction for each state-changing ledger operation.

What

  • Audited operations: account create, rename, status change, transaction post, transaction reversal. Each writes exactly one row inside its existing @Transactional boundary, so a rolled-back operation leaves no row (atomic).
  • Captured fields: actor_id (JWT subject), correlation_id (from MDC, generated fallback when absent), action, resource_type, resource_id, result (SUCCESS), request_hash (SHA-256 of the request body for create/post/reverse, null for the body-less status change and rename).
  • Reversal context: new nullable payload JSONB column (migration 019) stores the compensating transaction id and the optional reason; the reverse endpoint gains an optional request body carrying reason.
  • The audit write reuses the active-transaction guard pattern from the outbox publisher.
  • Idempotency action lambda widened to (String) -> StoredResponse so the already-computed request hash reaches the service without recomputation.

Scope

Success-path auditing only. Two follow-ups were split out deliberately:

Verification

  • Local gates green: :test :compileIntegrationTestKotlin :detekt :spotlessCheck :assemble (integrationTest runs on CI).
  • Migration 019 is idempotent (ADD COLUMN IF NOT EXISTS), nullable, registered once after 018; the append-only trigger is unaffected.
  • OpenAPI contract unchanged (no new path; only an optional body on the existing reverse path).

Closes #49

@tanya_r added 3 commits June 15, 2026 11:20
Write one immutable platform.audit_events row inside the business
transaction for account create, rename, status change, transaction
post and reversal. Capture actor (JWT subject), correlation id (MDC),
action, resource type and id, result and the SHA-256 request hash;
the reversal row carries the reason and compensating transaction id
in a new nullable JSONB payload column (migration 019). The audit
write reuses the active-transaction guard so a rolled-back operation
leaves no row. Failure and denied auditing are tracked separately.

Closes #49
…lation

Import AuditTrailWriterImpl into the @DataJpaTest contexts that pull in
AccountServiceImpl or TransactionPoster (they now require the audit
writer), adding an ObjectMapper bean to the idempotency IT. Stop
deleting platform.audit_events rows directly: the append-only trigger
rejects DELETE, so the writer ITs assert per-action row counts under
auto-rollback and the standalone writer IT truncates between tests.
Replace the unfinished retry-topology stub with a real happy-path drive
through the idempotency to poster chain.
Integration tests run in parallel forks against one shared Postgres
container, so audit rows committed by the standalone writer IT and the
full-context API ITs are visible to the auto-rollback ITs reading in
parallel. Assert by the unique resource id (and action) of each case
instead of global or action-only counts, parse the JSONB payload rather
than matching its exact text (Postgres reformats it on read), and stop
truncating in the writer IT to avoid cross-fork lock contention.
@tiana-code
tiana-code merged commit 9be758d into main Jun 15, 2026
6 checks passed
@tiana-code
tiana-code deleted the feat/E-01-audit-write-path branch June 15, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Write audit_events row per ledger write (actor, correlation, request hash)

1 participant