MOD-158 — Test seed data loader¶
Purpose¶
MOD-158 loads deterministic seed data into the dev and UAT Neon branches on first deploy to a fresh stage. Customers, accounts, and the GL chart are cross-domain entities that no single per-domain module owns; centralising their seeding here gives every integration test a known baseline without each team owning fixture-data plumbing.
FR scope: requirements: [] per the wiki. Tooling module.
Tests map to the spec's documented behaviour, not FR-NNN.
Architectural decisions: ADR-045.
Architecture¶
sst deploy
│
├── creates seed-loader Lambda + IAM role + log group
└── creates aws.lambda.Invocation (Custom Resource)
│ triggers map carries SEED_VERSION + stage
▼
synchronous Lambda invocation
│
├── reads /bank-platform/{stage}/seed-version from SSM
├── decideReload(recorded, SEED_VERSION):
│ null → truncate-and-reload
│ same → skip (no-op)
│ +patch → truncate-and-reload
│ +minor → additive (ON CONFLICT DO NOTHING)
│ +major → truncate-and-reload
├── if not skip:
│ fetch bank-neon/{branch}/bank_core/migrate_user
│ apply schema DDL (CREATE IF NOT EXISTS)
│ INSERT customers / accounts / gl_accounts
│ write SEED_VERSION to SSM
└── return { action, counts, duration_ms }
Deployment scope¶
dev and uat ONLY. sst.config.ts short-circuits when
stage === 'prod'. Single source of truth: src/config/stages.ts
(SEED_DEPLOY_STAGES = ['dev', 'uat']). Unit-tested in
__tests__/unit/stages-prod-skip.test.ts.
| Stage | What happens |
|---|---|
dev |
Lambda + Custom Resource provisioned; loads dev profile (10 customers, 13 accounts) |
uat |
Same; loads uat profile (103 customers, 200+ accounts) |
prod |
No resources. No Lambda, no Custom Resource, no SSM seed-version key |
Versioning (SEED_VERSION constant)¶
src/config/seed-version.ts exports SEED_VERSION = "1.0.0".
| Bump | Effect |
|---|---|
| Patch (1.0.0 → 1.0.1) | truncate seed tables, reload from scratch |
| Minor (1.0.0 → 1.1.0) | additive — new rows appended via ON CONFLICT DO NOTHING |
| Major (1.0.0 → 2.0.0) | truncate + reload |
Pulumi's aws.lambda.Invocation triggers on SEED_VERSION change,
so a constant bump auto-triggers reload on the next sst deploy.
The Lambda is also internally idempotent so manual invocations or
re-deploys without bumps are safe no-ops.
Seed profile contract¶
Both profiles are pure data definitions in src/profiles/:
- dev (
dev.ts): 10 customers (CUST-D001..D010), 13 accounts, 11 GL codes. Mirrorsbank-wiki/source/pages/operations/seed-data.mdexactly. CUST-D009 haskyc_status=Rejected; CUST-D010 hassanctions_status=Hit. - uat (
uat.ts): superset of dev — 10 dev customers passthrough - 90 deterministically-generated customers (CUST-U001..U090)
- 3 edge-case rows (Pending / Dormant / Restricted). 90 generated
customers come from
SHA256(seed20260427:ix:salt) → uint32for every random choice (jurisdiction, name, DOB, account types, balances). Same SEED_VERSION → identical rows on every load.
eIDV doc refs use the prefixes MOD-157 stubs match on:
PASS-*, FAIL-*, REFER-*. Seeded customers are designed to
trigger known stub outcomes — bridging MOD-158 (data) with MOD-157
(provider behaviour) gives integration tests deterministic input
on both sides.
Schema MOD-158 owns¶
Two schemas in the bank_core Neon database:
customers.customers— customer_id PK, full_name, jurisdiction, date_of_birth, eidv_doc_ref, kyc_status, sanctions_status,seeded_by_mod_158boolean flag, created_at.accounts.accounts— account_id PK, customer_id, account_type, currency, balance (numeric(20,2)), state,seeded_by_mod_158, created_at.accounts.gl_accounts— gl_code PK, account_name, account_class, currency,seeded_by_mod_158.
Per-domain modules (MOD-007 account state machine, MOD-009 KYC, etc.)
ALTER these tables to add columns they own. The seeded_by_mod_158
flag scopes truncate-on-reset to seed-owned rows only.
Reset¶
UAT requires --confirm because the reset truncates seed tables AND
deletes accumulated synthetic transaction history (MOD-159's output
into the same tables). The CLI:
- Resolves the Lambda function name from
/bank/{stage}/mod158/seed-loader/fn-name. - Invokes synchronously with
{trigger: "reset", reset: true}. - Lambda deletes the SSM version key, runs full truncate-and-reload,
re-writes the version key, returns
{action: "reset", counts}.
SSM outputs¶
Module-internal¶
| Path | Value | Consumed by |
|---|---|---|
/bank/{stage}/mod158/seed-loader/fn-name |
Lambda name | seed-reset CLI |
/bank/{stage}/mod158/seed-loader/fn-arn |
Lambda ARN | Ops |
/bank/{stage}/mod158/seed-loader/log-group-arn |
Log group | MOD-076 dashboards |
Cross-module contract (the FR-734 SSM-verifiable key)¶
| Path | Value | Owner |
|---|---|---|
/bank-platform/{stage}/seed-version |
Current loaded SEED_VERSION (e.g. 1.0.0) |
Lambda writes; orchestrator reads to verify |
The version key is the authoritative record of which seed profile is loaded. Per ADR-045, never manually insert seed-table rows without bumping this key.
Dependencies¶
- MOD-103 — Neon branches (
dev,uat) and per-database role secrets (bank-neon/{branch}/bank_core/migrate_user). - MOD-043 — EventBridge schema registry. (MOD-158 doesn't use it directly today; reserved for future schema-validated event emission of seed-customer changes.)
- MOD-045 — Secrets Manager infrastructure (the secret values come from MOD-103 but MOD-045 owns the encryption keys).
- MOD-157 — provider stubs. Seed
eidv_doc_refvalues (PASS-NZ-D001etc.) trigger known stub outcomes; MOD-157 must be deployed for end-to-end test flows to behave deterministically.
Constraints¶
- No prod deploy. Three layers: stages allow-list, sst.config
short-circuit, workflow
inputs.stage.optionsexcludes prod. - bank_core database only. Seed customers and accounts are
cross-domain banking entities that live in
bank_core. If a future profile needs to seed bank_kyc-specific data (e.g. KYC check records), extend the loader to multi-database — for now, per-domain seed data is each domain module's responsibility. - No FK constraints across schemas. MOD-158 doesn't enforce customer→account integrity at the SQL level so per-domain modules can ALTER the tables independently without ordering constraints. The loader inserts customers before accounts in transaction order; the data layer's referential integrity is application-level.
seeded_by_mod_158flag preserved. Per-domain modules that ALTER these tables must NOT drop the flag column — it's how reset identifies which rows to truncate.