Skip to content

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:

  1. bank-platform deploys MOD-158 → Lambda runs → parties table populated.
  2. 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:

  1. SD02 module deploys party.parties with its canonical schema.
  2. MOD-158 adds a migration that copies data from bank_platform_seed.parties to party.parties.
  3. MOD-158's seed loader cuts over — parties INSERT path goes to party.parties instead.
  4. bank_platform_seed.parties is dropped.
  5. Domain modules update their seed migrations: replace bank_platform_seed.parties with party.parties — a single s/bank_platform_seed.parties/party.parties/g replacement.

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