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
Activeaccounts;Pending,Restricted,Dormantskipped. - Daily limit — max 3 synthetic transactions per account per day.
- Idempotency —
synthetic_run_id = date:account_id:sequenceis 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.