Skip to content

MOD-159 — Synthetic transaction engine

Purpose

Generates synthetic banking activity on a schedule so dev and UAT look and behave like an operating bank rather than a set of deployed modules over an empty database. End-to-end pipeline exercise: ledger, balance, account state, validation, audit, notifications all interact on every run.

FR scope: requirements: []. Tooling module; tests map to spec behaviour, not FR-NNN.

Architectural decisions: ADR-045.

Architecture

EventBridge Scheduler (1× dev / 2× uat / DISABLED prod)
  └── transaction-generator Lambda
        ├── reads seed accounts from MOD-158-loaded customers.customers
        ├── reads current balances + states from accounts.accounts
        ├── for each Active account ≤ 3× per day:
        │     compute deterministic outcome via shared/transaction-mix
        │     publish PaymentInstructionRequested → bank-payments bus
        └── pipeline: MOD-020 → MOD-001 → MOD-003 → MOD-022 → MOD-063

Deployment scope

dev and uat ONLY. Stages allow-list in src/config/stages.ts; sst.config.ts short-circuits when stage === 'prod'. Schedule config also empty for prod. Workflow inputs.stage.options excludes prod. Unit-tested.

Stage Schedules Volume
dev 1× Tue+Thu 09:00 NZST 50 transactions/run
uat 2× weekdays 08:30 + 16:30 NZST 200 transactions/run
prod none n/a

Schedule activation gate

All schedules are created in DISABLED state by Pulumi. The post-deploy step in mod-159.yml checks SSM paths for MOD-001/003/007/020 and only enables when all four resolve:

/bank-core/{stage}/posting-engine/api-url     ← MOD-001
/bank-core/{stage}/balance-engine/api-url     ← MOD-003
/bank-core/{stage}/account-state/api-url      ← MOD-007
/bank-payments/{stage}/payment-validation/api-url ← MOD-020

Until those land, the Lambda + IAM + schedule exist but no synthetic events fire. Engine is dormant by design — no failed events, no DLQ pressure.

Transaction mix

Per ADR-045 / spec MOD-159.md §"Transaction mix":

Type Default Friday-run bias
retail-outbound 40 % 30 %
p2p-intra-bank 20 % 20 %
inbound-credit 25 % 30 % (payroll bias)
savings-transfer 10 % 15 %
fee-debit 5 % 5 %

Selection seeded by SHA256(stage + ISO-date + account_id) → uint32, so the same account on the same date in the same stage produces the same transaction across runs (debugging-friendly determinism).

Constraints (enforced in shared/transaction-mix.ts)

  • Balance check — debit ≤ 90% of current balance.
  • Account state — only Active accounts; Pending, Restricted, Dormant skipped.
  • Daily limit — max 3 synthetic transactions per account per day.
  • Idempotencysynthetic_run_id = date:account_id:sequence is the payment reference; MOD-001's idempotency_key rejects duplicates if the engine fires twice.

SSM outputs (operational)

Path Value
/bank/{stage}/mod159/generator/{fn-name,fn-arn} Lambda handle
/bank/{stage}/mod159/generator/log-group-arn Log group
/bank/{stage}/mod159/schedule/{0,1}/name Schedule names (per index)

Dependencies

  • MOD-158 — seed customers + accounts present in Neon.
  • MOD-001 / MOD-003 / MOD-007 / MOD-020 — required for schedule activation. Schedule stays DISABLED until all are deployed.
  • MOD-062 — optional; enables term-deposit / notice-period scenarios which aren't in the initial generator.

Constraints

  • No prod deploy.
  • The generator does NOT write to the database directly. All transactions go through real EventBridge events → real validation → real posting. If MOD-020/MOD-001 reject an event, the synthetic transaction is naturally dropped and an audit log entry exists.