Party & Counterparty SQL Support Design
Table of Contents
- Overview
- Stories
- Story 0: Add validations and custom indexes to domain entity codegen template
- Story 1: Add party-related reference data tables
- Story 2: Add party table
- Story 3: Add counterparty table
- Story 4: Add party and counterparty identifier tables
- Story 5: Add party and counterparty contact information tables
- Story 6: Add account-party association table
- Dependency Graph
- Existing Dependencies
- Open Questions
Overview
Add database-level support for parties (internal legal entities) and counterparties (external trading partners). This is the foundational data model for trade booking, regulatory reporting, and exposure management.
Key Domain Concepts
- Party ("the house"): An internal legal entity managed by the tenant. A tenant must have at least one party (the "root party") for the system to function. Parties form an arbitrary-depth hierarchy representing corporate group structures (e.g., BigCorp Group Ltd -> BigCorp Finance Ltd -> BigCorp Bank UK).
- Counterparty: An external legal entity that the tenant trades against. Counterparties also support hierarchy for exposure aggregation at the group level. Structurally identical to parties but semantically separate.
- Party Identifier: External identifiers attached to parties or counterparties (LEI, BIC, National ID, Tax ID, etc.).
- Contact Information: Address, phone, email, and web page for a party or counterparty. Typed by contact type (Legal, Operations, Settlement, Billing).
- Account-Party Association: Links user accounts to parties, enabling users to select which party context to work in on login.
Design Decisions
- Separate tables for party and counterparty: Though structurally identical today, parties and counterparties serve different business purposes. Parties are internal and tightly coupled to IAM (account association, root party constraint). Counterparties are external and may diverge in structure over time (e.g., credit ratings, exposure limits). Separate tables provide clean data isolation.
- Arbitrary hierarchy depth: Both parties and counterparties use a
self-referencing
parent_party_id/parent_counterparty_idfor hierarchy. No depth limitation is imposed at the schema level. - Root party constraint: A tenant must have exactly one root party
(
parent_party_id IS NULL), enforced via a partial unique index. Counterparties have no such constraint. - Status as coding scheme: Party/counterparty status (Active, Inactive, Suspended) is a reference data table rather than a check constraint, to support future state machine evolution.
- Identifiers as separate table: LEI, BIC, and other identifiers are extracted into a dedicated table rather than inline columns. This is more extensible and follows FpML patterns.
- Contact type scheme: Contact information includes a
contact_typecolumn (Legal, Operations, Settlement, Billing) to support multiple addresses per party. When business units are added later, abusiness_unit_idcolumn can be added to the same table. - Soft FK validation: Foreign key references to reference data tables (party_type, status, business_centre, country) are validated in the insert trigger rather than via hard FK constraints. This follows the established codebase pattern.
- Codegen first: All tables will be generated from JSON models using
ores.codegen. Thedomain_entitytemplate needs enhancement to supportvalidations[]andindexes[], which the oldertable_createtemplate already provides.
Stories
Stories are listed in dependency order. Each story produces a PR.
Story 0: Add validations and custom indexes to domain entity codegen template
The sql_schema_domain_entity_create.mustache template currently lacks two
features that sql_schema_table_create.mustache already has:
validations[]: Soft FK validation calls in the insert trigger function. Allows the model to declare columns that should be validated against reference data tables.indexes[]: Custom index definitions beyond the auto-generated natural key and version indexes. Supports unique, non-unique, and filtered indexes.
Both features are needed for the party/counterparty tables to validate
party_type, status, and business_center_code via soft FKs, and to create
the root party uniqueness constraint.
Scope
Port these two features from sql_schema_table_create.mustache (lines 121-126
for indexes, lines 162-166 for validations) into
sql_schema_domain_entity_create.mustache.
Acceptance Criteria
- Domain entity models can declare
validations[]withcolumnandvalidation_functionfields. - Domain entity models can declare
indexes[]withname,columns,unique, andcurrent_onlyfields. - Existing domain entity models continue to generate identical output.
- Template changes are tested by generating an existing model and diffing output.
Story 1: Add party-related reference data tables
Create reference data tables for party type, party status, party identifier scheme, and contact type. These are prerequisites for the party and counterparty tables.
ores_refdata_party_types_tbl
Categorises parties and counterparties by organisation type.
| Code | Description |
|---|---|
| Bank | Banking institution |
| CorporateGroup | Corporate holding group |
| HedgeFund | Hedge fund |
| Corporate | Non-financial corporate |
| CentralBank | Central bank / monetary authority |
| Exchange | Exchange or trading venue |
| Individual | Individual / personal account |
Uses table_create codegen template (text PK). Generates
ores_refdata_validate_party_type_fn(). Populated via dataset.
ores_refdata_party_statuses_tbl
Lookup table for party/counterparty status values. Designed to evolve into a more complex state machine over time.
| Code | Description |
|---|---|
| Active | Party is active, trades can be booked |
| Inactive | Party is deactivated |
| Suspended | Party is temporarily suspended |
Uses table_create codegen template. Generates
ores_refdata_validate_party_status_fn(). Populated via dataset.
ores_refdata_party_id_schemes_tbl
Defines what kinds of external identifiers can be attached to parties and counterparties.
| Code | Description |
|---|---|
| LEI | ISO 17442 Legal Entity Identifier |
| BIC | ISO 9362 Business Identifier Code |
| NationalId | National identification number |
| TaxId | Tax identification number |
| InternalCode | Internal system code |
Uses table_create codegen template. Generates
ores_refdata_validate_party_id_scheme_fn(). Populated via dataset.
ores_refdata_contact_types_tbl
Coding scheme for contact/address types. Supports multiple addresses per party.
| Code | Description |
|---|---|
| Legal | Legal / registered address |
| Operations | Operational / trading address |
| Settlement | Settlement instructions address |
| Billing | Billing address |
Uses table_create codegen template. Generates
ores_refdata_validate_contact_type_fn(). Populated via dataset.
Acceptance Criteria
- All four tables created with full temporal support, triggers, notification, soft delete.
- Validation functions generated and working.
- Population scripts seed initial values.
- pgTAP tests verify trigger behaviour and validation.
- Orchestration scripts updated (create, drop, populate).
Story 2: Add party table
ores_refdata_parties_tbl - internal parties ("the house").
Table Structure
| Column | Type | Constraints |
|---|---|---|
id |
UUID | PK |
tenant_id |
UUID | not null |
full_name |
text | not null, natural key |
short_code |
text | not null, natural key |
party_type |
text | not null, soft FK -> party_types |
parent_party_id |
UUID | nullable (NULL = root party) |
business_center_code |
text | nullable, soft FK -> business_centres |
status |
text | not null, default 'Active', soft FK -> statuses |
| + audit columns | version, modified_by, etc. |
Validations (in insert trigger)
party_type->ores_refdata_validate_party_type_fn()status->ores_refdata_validate_party_status_fn()business_center_code->ores_refdata_validate_business_centre_fn()(exists)parent_party_id-> custom validation: must reference a valid current party in the same tenant, or be NULL
Custom Indexes
Root party uniqueness constraint:
create unique index ores_refdata_parties_root_party_uniq_idx on ores_refdata_parties_tbl (tenant_id) where parent_party_id is null and valid_to = ores_utility_infinity_timestamp_fn();
Codegen
Uses domain_entity template (with Story 0 enhancements). Model file:
projects/ores.codegen/models/refdata/party_domain_entity.json.
Acceptance Criteria
- Table created with full temporal support, triggers, notification, soft delete.
- Root party constraint enforced (one per tenant).
- Parent party validation rejects invalid/cross-tenant references.
- Business centre, party type, and status soft FK validation working.
- Notification trigger on
ores_partieschannel. - pgTAP tests for all validation rules and constraints.
- Orchestration scripts updated.
Story 3: Add counterparty table
ores_refdata_counterparties_tbl - structurally identical to party table, minus
the root party uniqueness constraint.
Table Structure
| Column | Type | Constraints |
|---|---|---|
id |
UUID | PK |
tenant_id |
UUID | not null |
full_name |
text | not null, natural key |
short_code |
text | not null, natural key |
party_type |
text | not null, soft FK -> party_types |
parent_counterparty_id |
UUID | nullable (NULL = top-level counterparty) |
business_center_code |
text | nullable, soft FK -> business_centres |
status |
text | not null, default 'Active', soft FK -> statuses |
| + audit columns | version, modified_by, etc. |
Validations
Same as party table, except:
parent_counterparty_idvalidates againstores_refdata_counterparties_tbl(self-referencing within counterparties, not parties).- No root counterparty constraint.
Codegen
Uses domain_entity template. Model file:
projects/ores.codegen/models/refdata/counterparty_domain_entity.json.
Acceptance Criteria
- Table created with full temporal support, triggers, notification, soft delete.
- Parent counterparty validation rejects invalid/cross-tenant references.
- Counterparty hierarchy supports arbitrary depth.
- Notification trigger on
ores_counterpartieschannel. - pgTAP tests.
- Orchestration scripts updated.
Story 4: Add party and counterparty identifier tables
Two structurally identical tables for external identifiers.
Table Structure (both tables)
ores_refdata_party_identifiers_tbl / ores_refdata_counterparty_identifiers_tbl
| Column | Type | Constraints |
|---|---|---|
id |
UUID | PK |
tenant_id |
UUID | not null |
party_id / counterparty_id |
UUID | not null, soft FK |
id_scheme |
text | not null, soft FK -> party_id_schemes |
id_value |
text | not null |
description |
text | nullable |
| + audit columns | version, modified_by, etc. |
Custom Indexes
Unique constraint: one identifier of each scheme per party:
create unique index ores_refdata_party_identifiers_party_scheme_uniq_idx on ores_refdata_party_identifiers_tbl (tenant_id, party_id, id_scheme) where valid_to = ores_utility_infinity_timestamp_fn();
Codegen
Uses domain_entity template. Model files:
projects/ores.codegen/models/refdata/party_identifier_domain_entity.jsonprojects/ores.codegen/models/refdata/counterparty_identifier_domain_entity.json
Acceptance Criteria
- Both tables created with full temporal support, triggers, notification.
- Scheme validation working.
- Uniqueness constraint prevents duplicate schemes per party.
- pgTAP tests.
- Orchestration scripts updated.
Story 5: Add party and counterparty contact information tables
Two structurally identical tables for contact information with inline addresses.
Table Structure (both tables)
ores_refdata_party_contact_informations_tbl /
ores_refdata_counterparty_contact_informations_tbl
| Column | Type | Constraints |
|---|---|---|
id |
UUID | PK |
tenant_id |
UUID | not null |
party_id / counterparty_id |
UUID | not null, soft FK |
contact_type |
text | not null, soft FK -> contact_types |
street_line_1 |
text | nullable |
street_line_2 |
text | nullable |
city |
text | nullable |
state |
text | nullable |
country_code |
text | nullable, soft FK -> countries |
postal_code |
text | nullable |
phone |
text | nullable |
email |
text | nullable |
web_page |
text | nullable |
| + audit columns | version, modified_by, etc. |
Custom Indexes
One contact of each type per party:
create unique index ores_refdata_party_contact_informations_type_uniq_idx on ores_refdata_party_contact_informations_tbl (tenant_id, party_id, contact_type) where valid_to = ores_utility_infinity_timestamp_fn();
Future Extension
When business units are added, a nullable business_unit_id column will be
added to this table and the unique constraint adjusted to include it. The
existing structure and all address/phone/email fields remain unchanged.
Codegen
Uses domain_entity template. Model files:
projects/ores.codegen/models/refdata/party_contact_information_domain_entity.jsonprojects/ores.codegen/models/refdata/counterparty_contact_information_domain_entity.json
Acceptance Criteria
- Both tables created with full temporal support, triggers, notification.
- Contact type and country code validation working.
- Uniqueness constraint prevents duplicate contact types per party.
- pgTAP tests.
- Orchestration scripts updated.
Story 6: Add account-party association table
ores_iam_account_parties_tbl - many-to-many link between user accounts and
internal parties. Enables users to select which party context to work in on
login.
Table Structure
| Column | Type | Constraints |
|---|---|---|
account_id |
UUID | left FK (junction) |
party_id |
UUID | right FK (junction) |
tenant_id |
UUID | not null |
| + audit columns |
Business Rules
- An account can be associated with zero or more parties.
- Zero associations = admin-only account (tenant management, no trade context).
- When a user logs in with multiple party associations, they must select a party context.
- A user with exactly one party association is automatically placed in that context.
Codegen
Uses junction codegen template. Model file:
projects/ores.codegen/models/iam/account_party_junction.json.
Note: Lives in ores_iam_ component since it extends the IAM model, not in
ores_refdata_.
Acceptance Criteria
- Junction table created with temporal support.
- Both account_id and party_id validated.
- Notification trigger on
ores_account_partieschannel. - pgTAP tests.
- Orchestration scripts updated.
Dependency Graph
Story 0 (codegen enhancement)
|
v
Story 1 (reference data: party_types, party_statuses, party_id_schemes, contact_types)
|
+---> Story 2 (party table)
| |
| +---> Story 4 (party identifiers)
| +---> Story 5 (party contact information)
| +---> Story 6 (account-party junction)
|
+---> Story 3 (counterparty table)
|
+---> Story 4 (counterparty identifiers)
+---> Story 5 (counterparty contact information)
Stories 2 and 3 can be developed in parallel once Story 1 is complete. Stories 4 and 5 can be developed in parallel once their parent (2 or 3) is complete. Story 6 depends only on Story 2.
Existing Dependencies
The following existing database objects are referenced:
| Object | Status |
|---|---|
ores_iam_validate_tenant_fn() |
Exists |
ores_dq_validate_change_reason_fn() |
Exists |
ores_refdata_validate_business_centre_fn() |
Exists |
ores_refdata_countries_tbl |
Exists |
ores_iam_accounts_tbl |
Exists |
Open Questions
None at this time. All design decisions have been validated.