Skip to content

feat: canonical audit logAction helpers, Horizon network-failure retry coverage, SEP-24 standard validation envelope - #850

Merged
Cjay-Cyber-2 merged 2 commits into
mergepay:mainfrom
EniolaTrev:feat/audit-logging-horizon-retry-sep24-validation
Oct 19, 2026
Merged

Cjay-Cyber-2 merged 2 commits into
mergepay:mainfrom
EniolaTrev:feat/audit-logging-horizon-retry-sep24-validation

Conversation

@EniolaTrev

Copy link
Copy Markdown
Contributor

Closes #660
Closes #681
Closes #687
Closes #688

Summary

This branch addresses four assigned issues together, in the way the repo's own history has handled them: each feature is already largely present from earlier upstream merges, so the work here is an audit against the acceptance criteria, closing the concrete gaps the audit found, and adding the missing proof — while leaving the existing, tested architecture intact.

The concrete deliverables added by this PR:

Changes in this PR

Area File Change
Audit helper (#660) src/services/audit.ts LogActionParams, logAction, logActionTx (maps issue vocabulary → persisted columns)
Audit call sites (#660/#681) src/routes/groups.ts, src/routes/expenses.ts, src/routes/settlements.ts, src/services/settlement-machine.ts group.create, group.join, expense.create, settlement.create, settlement.status_changed now write through logActionTx
Audit tests (#660/#681) tests/services/audit-logAction.test.ts (new, 7 tests), tests/audit-state-changing-operations.test.ts (new, 3 tests) Column mapping, sanitization/redaction, best-effort vs transactional failure, structured mirror; persisted rows for group creation and member addition
Horizon retry tests (#688) tests/horizon-network-failure-retry.test.ts (new, 6 tests) Transport/timeout retries, exponential backoff, budget exhaustion + final error, configurable maxAttempts, permanent tx_* rejection, Pino retry log fields
SEP-24 envelope tests (#687) tests/sep24-standard-validation-envelope.test.ts (new, 28 tests) The standard 400 envelope on deposit, withdraw, transaction, transactions, callback — both Zod and ajv boundaries, plus controls
Test-harness upkeep tests/audit-actor-attribution.test.ts, tests/settlement-reconciliation.test.ts, tests/settlement-memo-integration.test.ts The audited-write inventory now counts logAction* helpers; two module mocks expose the new logActionTx export

Acceptance criteria, mapped

#660 — AuditLog migration + Prisma repository service

  • AuditLog model in prisma/schema.prisma — prisma/schema.prisma:495 (id, actor userId, action, resource entityType/entityId, metadata JSON, details JSON, createdAt timestamp; indexes on userId, groupId, (entityType, entityId); mapped to audit_logs).
  • Migration tooling / client generation — migrations checked in: prisma/migrations/20260614120000_init (creates audit_logs), 20260725000001_add_audit_log, 20260728000000_add_audit_log_group_id; npx prisma generate runs clean (verified on this branch).
  • src/services/audit.ts provides logAction(...) used across route handlers — new logAction / logActionTx in src/services/audit.ts; adopted in src/routes/groups.ts (group.create, group.join), src/routes/expenses.ts (expense.create), src/routes/settlements.ts (settlement.create), src/services/settlement-machine.ts (settlement.status_changed).
  • Unit tests verifying audit-log creation on group and expense mutations — tests/services/audit-logAction.test.ts (mapping, null actor for worker/system, metadata sanitization, structured mirror, rollback semantics); tests/audit-state-changing-operations.test.ts asserts the exact rows handed to prisma.auditLog.create for group creation and member addition; expense audit writes are covered by tests/expense-action-audit.test.ts and the unchanged route suites.

#681 — Audit logging for state-changing group/treasury operations

  • Audit log model updated if needed — model present with groupId (migration 20260728000000_add_audit_log_group_id), so every group-scoped row can be queried per group.
  • Helper in src/services/ — src/services/audit.ts: durable audit / auditTx, canonical logAction / logActionTx, plus auditGroupMemberActionTx and auditMultisigActionTx for membership and multisig events.
  • Integrated into key state-changing endpoints — group creation and member addition (src/routes/groups.ts), expense creation (src/routes/expenses.ts), settlement creation and status transitions (src/routes/settlements.ts, src/services/settlement-machine.ts), treasury multisig proposal/sign/submit (src/routes/treasury.ts via auditMultisigActionTx).
  • Tests verifying entries are persisted on successful state changes — new tests/audit-state-changing-operations.test.ts (group.create + group.join rows with actor, target, outcome: "success", actorType: "user"); existing tests/integration/group-expense-database.test.ts asserts group.create / group.join rows end-to-end; tests/treasury-multisig-audit.test.ts covers the multisig rows.
  • Sensitive-data safety — sanitizeAuditMetadata redacts privateKey / secret / token / xdr keys to [REDACTED]; asserted in tests/services/audit-logAction.test.ts. No private key material or signed envelopes are ever persisted or logged.
  • Design note (deliberate, not an omission): the issue titles this "middleware", but the repo's convention is transactional in-handler audit (logActionTx inside the same prisma.$transaction as the mutation). A post-hoc request middleware would (a) double-log every route that already audits, and (b) break the atomicity guarantee — an audit row would survive a rolled-back mutation. The transactional helpers are the stronger implementation of the same requirement.

#687 — Zod validation for SEP-24 deposit/withdrawal

  • Dedicated Zod schemas — src/validations/sep24.ts (sep24DepositRequestSchema, sep24WithdrawRequestSchema, sep24InitQuerySchema, sep24TransactionQuerySchema, sep24TransactionsQuerySchema, sep24CallbackQuerySchema), re-exported from src/schemas/sep24.ts, which also exports the validateSep24Deposit / validateSep24Withdraw Fastify middleware named in the issue.
  • Integrated into src/routes/sep24.ts — the deposit/withdraw preHandlers parse query + body before any anchor I/O; the transaction, transactions, and callback handlers parse inline before any database work.
  • Standard 400 with clear formatting — src/plugins/error-handler.ts renders both ZodError and Fastify/ajv FST_ERR_VALIDATION failures as 400 with { error: { code: "VALIDATION_ERROR", message, timestamp, requestId, details, issues } } and legacy top-level mirrors. New tests/sep24-standard-validation-envelope.test.ts proves the identical envelope on all five surfaces, including the ajv boundary and unrecognized-key rejection, and asserts no anchor call or DB read happens first.
  • Unit tests for invalid payload rejection — existing suites (tests/sep24-request-validation.test.ts, tests/sep24-callback-validation.test.ts, tests/sep24-init-validation.test.ts, tests/sep24-validation.test.ts, tests/sep24-transaction-status.test.ts, tests/sep24-transactions-list.test.ts) plus the new consolidated envelope suite (28 tests).

#688 — Horizon retry logic in the background worker

  • Exponential backoff utility in src/services/ — src/services/horizon-retry.ts: withHorizonRetry with classifyHorizonError (transient / indeterminate / permanent), horizonRetryDelayMs (exponential + jitter), HORIZON_RETRY_POLICY; and src/services/retry.ts (withRetry + defaultReadPolicy).
  • Specific exception handling + Pino logging — TransportError/TimeoutError (src/services/timeout.ts) are classified explicitly; logRetryAttempt (src/services/retry.ts:471) emits structured per-attempt Pino records and is wired through src/services/stellar.ts, src/services/anchor.ts, src/services/network.ts. New test pins the exact log fields.
  • Worker retries polling up to a configurable maximum — src/worker/tasks/tx-status-sync.ts wraps every Horizon lookup in withHorizonRetry; attempt budgets come from config: HORIZON_READ_RETRY_MAX_ATTEMPTS (src/services/horizonService.ts), UPSTREAM_RETRY_MAX_ATTEMPTS (src/services/retry.ts), and job-level WORKER_SETTLEMENT_RETRY_* / WORKER_ANCHOR_RETRY_* (src/services/job-retry.ts).
  • Unit tests simulating network failures — new tests/horizon-network-failure-retry.test.ts (retry-then-succeed, timeout as indeterminate, exhaustion preserving the last error, configurable maxAttempts); existing tests/horizon-retry.test.ts, tests/horizon-query-retry.test.ts, tests/stellar-horizon-retry.test.ts, tests/network-retry.test.ts, tests/worker/health.test.ts remain green.

Tests & validation

  • npm test — 200 test files, 4077 passed, 22 skipped (baseline before this branch: 196 files / 4033 passed — the four new suites add 44 tests).
  • npm run build (tsc strict) — clean.
  • npm run lint — 0 errors (pre-existing no-unused-vars warnings unchanged).
  • Tests run fully offline: Prisma and the anchor/Horizon clients are mocked, no database or network is touched.
  • No secrets in tests or audit records: assertions in tests/services/audit-logAction.test.ts and the SEP-24 callback suite mock only public material.

Reviewer notes

  • The repository already shipped most of these four features; the PR body above separates "verified as already enforced" from "added here" so the diff is easy to review against the criteria.
  • The only behaviour changes are additive: new helper names, actorType/outcome metadata on two group audit rows, and wider matching in an existing inventory test. No route contracts changed.

…EP-24 400 envelope

- mergepay#660: add canonical logAction/logActionTx repository helpers to
  src/services/audit.ts and adopt them at the group, expense and
  settlement state-changing call sites (atomic, sanitized metadata).
- mergepay#681: record actor type and outcome on group.create / group.join audit
  rows; add route-level tests proving the persisted rows.
- mergepay#688: add network-failure retry tests for Horizon polling covering
  TransportError/TimeoutError retries, budget exhaustion, permanent
  rejection, configurable attempts, and structured Pino logging.
- mergepay#687: add a consolidated standard-400 VALIDATION_ERROR envelope test
  across every SEP-24 surface (deposit, withdraw, transaction,
  transactions, callback), including both validator boundaries.

Closes mergepay#660
Closes mergepay#681
Closes mergepay#687
Closes mergepay#688
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@EniolaTrev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Cjay-Cyber-2 Cjay-Cyber-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good — approving.

@Cjay-Cyber-2 Cjay-Cyber-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good — approving.

@Cjay-Cyber-2
Cjay-Cyber-2 merged commit 99a95a5 into mergepay:main Oct 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment