Seed data consumers guide¶
This guide tells every domain module how to write its own dev/UAT seed data using Flyway migrations. Read this page before writing any seed migration.
Single source of truth for party identities:
bank_platform_seed.parties (owned by MOD-158).
Domain modules join against it — they do not duplicate party rows.
What MOD-158 owns (and only this)¶
| Object | Type | Purpose |
|---|---|---|
bank_platform_seed schema |
namespace | MOD-158-owned — no other module DDLs here |
bank_platform_seed.parties |
table | Canonical 100+ test parties for dev/UAT. Stub for SD02 party.parties until SD02 deploys. |
bank_platform_seed.markers |
table | Shared idempotency-tracking. Optional for domain modules. |
MOD-158 does not model accounts, products, KYC records, payments, or any domain-specific data. Those are owned by each domain module's own Flyway migrations.
What domain modules own¶
Each domain module (MOD-001 posting engine, MOD-009 KYC, MOD-020 payment validation, etc.) owns:
- Its own table schemas via Flyway migrations — already true.
- Its own dev/UAT seed data via Flyway migrations alongside the schema — new responsibility.
- Its own integration tests that assert seed data is present in the right shape.
When schema changes, the seed migration changes in the same PR by the same author. No cross-repo handshake and no PR to MOD-158 needed.
How to reference MOD-158 parties from your Flyway migration¶
By the time your seed migration runs, bank_platform_seed.parties is
populated by MOD-158's Lambda with the canonical party rows — 10 dev parties
(CUST-D001..CUST-D010) or 103 UAT parties. Each row has a stable
party_ref and a stable party_id (UUID).
See Seed data reference for the full party_ref catalogue.
Single-row join on party_ref¶
-- in your module's Flyway seed file
INSERT INTO accounts.account_party_relationships
(account_id, party_id, relationship_type, can_transact, can_view, dcs_relevant, start_date)
SELECT
'<account-uuid>'::uuid,
p.party_id,
'ACCOUNT_HOLDER',
true, true, true,
'2024-01-15'::date
FROM bank_platform_seed.parties p
WHERE p.party_ref = 'CUST-D001';
Bulk-CTE pattern (preferred for multi-row inserts)¶
WITH seeds(party_ref, account_id) AS (VALUES
('CUST-D001', '<acc-uuid-1>'::uuid),
('CUST-D002', '<acc-uuid-2>'::uuid),
('CUST-D003', '<acc-uuid-3>'::uuid)
)
INSERT INTO accounts.account_party_relationships
(account_id, party_id, relationship_type, can_transact, can_view, dcs_relevant, start_date)
SELECT s.account_id, p.party_id, 'ACCOUNT_HOLDER', true, true, true, '2024-01-15'::date
FROM seeds s
JOIN bank_platform_seed.parties p ON p.party_ref = s.party_ref;
The CTE declares your domain's local shape (account IDs, order, etc.);
the JOIN fetches the canonical party_id without duplicating party data.
Stage-conditional seeding¶
Domain seed migrations must run only in dev and UAT — never in prod. Two patterns:
Pattern A — stage GUC¶
DO $$
BEGIN
IF current_setting('app.stage', true) IN ('dev', 'uat') THEN
INSERT INTO accounts.accounts (...) VALUES (...);
END IF;
END $$;
Requires app.stage to be set on the connection. Bank-platform's Neon role
config sets it via ALTER ROLE migrate_user SET app.stage = 'dev'.
Pattern B — separate seed directory¶
Use Flyway's flyway.locations config to include a seeds/ directory only in dev/UAT:
# bank-core/flyway/dev.conf
flyway.locations=filesystem:./schema,filesystem:./seeds
# bank-core/flyway/prod.conf
flyway.locations=filesystem:./schema
Either pattern works — choose the one that fits your repo's CI setup.
Idempotency tracking — optional markers convention¶
If you want re-seeds to clean up after themselves (rather than relying on
INSERT ... ON CONFLICT DO NOTHING), use MOD-158's markers table:
-- After inserting a row:
INSERT INTO bank_platform_seed.markers
(table_schema, table_name, primary_key, seed_version)
VALUES ('accounts', 'accounts', '<uuid>', 'mod-001-dev-seed-v1');
To re-seed, delete via the marker join — never touches non-seed rows:
DELETE FROM accounts.accounts a
USING bank_platform_seed.markers m
WHERE m.table_schema='accounts'
AND m.table_name='accounts'
AND m.primary_key = a.id::text;
DELETE FROM bank_platform_seed.markers
WHERE table_schema='accounts' AND table_name='accounts';
Use this convention if you want deterministic reset; omit it if ON CONFLICT
DO NOTHING is sufficient for your module.
Deploy ordering¶
MOD-158's Lambda must run before any domain module's seed migration that
references bank_platform_seed.parties. Natural ordering:
bank-platformdeploys MOD-158 → Lambda runs → parties table populated.- Domain repos (
bank-core,bank-kyc, …) deploy → Flyway runs → seed migrations find the populated parties table.
If a domain deploys first and runs its seed migration before the parties table
exists, Flyway fails with "relation bank_platform_seed.parties does not
exist". That is the correct failure — it surfaces the missing prerequisite.
Operator action: deploy bank-platform first, then re-run the domain deploy.
When SD02 deploys (party.parties)¶
Migration path, owned by MOD-158:
- SD02 module deploys
party.partieswith its canonical schema. - MOD-158 adds a migration that copies data from
bank_platform_seed.partiestoparty.parties. - MOD-158's seed loader cuts over — parties INSERT path goes to
party.partiesinstead. bank_platform_seed.partiesis dropped.- Domain modules update their seed migrations: replace
bank_platform_seed.partieswithparty.parties— a singles/bank_platform_seed.parties/party.parties/greplacement.
The cutover happens in one MOD-158 release; domain modules adapt at their own cadence on the next release after that.
Reference implementation — MOD-001 dev seed¶
bank-platform/MOD-158-test-seed-data-loader/docs/snippets/MOD-001-dev-seed.sql
is the canonical copy-paste starter for bank-core's accounts.* Flyway seed.
Its structure shows the bulk-CTE pattern and markers convention in production use:
-- 13 accounts for 10 dev parties (D001-D010, some with savings accounts)
INSERT INTO accounts.accounts
(id, account_number, product_code, currency, status,
balance, available_balance, overdraft_limit, jurisdiction,
opened_at, updated_at)
VALUES
-- D001: NZ transaction + savings
(gen_random_uuid(), '01-1001-0000001-00', 'NZ_TXN_01', 'NZD', 'ACTIVE', 12500.00, 12500.00, 0.00, 'NZ', '2024-01-15T00:00:00Z', now()),
(gen_random_uuid(), '01-1001-0000002-00', 'NZ_SAV_01', 'NZD', 'ACTIVE', 32000.00, 32000.00, 0.00, 'NZ', '2024-01-15T00:00:00Z', now()),
-- ... (10 dev parties, 13 accounts total)
-- D009 PENDING (KYC rejected), D010 RESTRICTED (sanctions hit)
(gen_random_uuid(), '01-1009-0000001-00', 'NZ_TXN_01', 'NZD', 'PENDING', 0.00, 0.00, 0.00, 'NZ', '2024-06-01T00:00:00Z', now()),
(gen_random_uuid(), '01-1010-0000001-00', 'NZ_TXN_01', 'NZD', 'RESTRICTED', 0.00, 0.00, 0.00, 'NZ', '2024-06-12T00:00:00Z', now())
ON CONFLICT (account_number) DO NOTHING;
-- Bulk-CTE to link accounts to parties via bank_platform_seed.parties
WITH seeds(account_number, party_ref) AS (VALUES
('01-1001-0000001-00', 'CUST-D001'),
('01-1001-0000002-00', 'CUST-D001'),
-- ...
('01-1009-0000001-00', 'CUST-D009'),
('01-1010-0000001-00', 'CUST-D010')
)
INSERT INTO accounts.account_party_relationships
(relationship_id, account_id, party_id, relationship_type,
ownership_share_pct, can_transact, can_view, dcs_relevant, start_date)
SELECT
gen_random_uuid(),
a.id,
p.party_id,
'ACCOUNT_HOLDER',
100.0000,
CASE WHEN s.party_ref IN ('CUST-D009','CUST-D010') THEN false ELSE true END,
true,
CASE WHEN s.party_ref IN ('CUST-D009','CUST-D010') THEN false ELSE true END,
'2024-01-15'::date
FROM seeds s
JOIN accounts.accounts a ON a.account_number = s.account_number
JOIN bank_platform_seed.parties p ON p.party_ref = s.party_ref
ON CONFLICT (relationship_id) DO NOTHING;
Key points from the reference:
- D009 and D010 get can_transact = false / dcs_relevant = false — the seed mirrors the edge-case identities from the party catalogue.
- ON CONFLICT DO NOTHING throughout — the seed is idempotent without markers.
- The optional markers block (at the end of the file) tracks rows for self-reset support.
See also¶
- Seed data reference — canonical party_ref catalogue (CUST-D001..CUST-U103, account numbers, expected KYC and sanctions statuses)
- Test environment setup — operational runbook for loading and resetting seed data