ores.refdata.party

Table of Contents

Internal legal entities (the organisation and its subsidiaries) that participate in financial transactions. Parties form a hierarchy through parent_party_id, with exactly one root party per tenant representing the organisation itself.

1. Flags

Cross-cutting properties. Consumed by both C++ and SQL codegen.

2. Columns

Logical, human-meaningful identifiers (full_name, short_code, flagged :natural_key: true below) are inserted into the C++ struct alongside the primary key, and exposed in SQL as additional unique-indexed columns.

2.1. id

UUID uniquely identifying this party.

Surrogate key for the party record.

2.2. full_name

Full legal name of the party.

The official registered name of the entity.

std::string(faker::company::companyName())

2.3. short_code

Short code for quick reference.

A brief mnemonic code used in trading systems.

std::string(faker::string::alpha(6))

2.4. codename

Globally unique human-readable codename (adjective_noun).

Used as the per-party pgmq queue prefix, pg_cron job names, and operator tooling. Immutable once assigned; auto-generated by the SQL trigger if not supplied on insert.

""

2.5. transliterated_name

ASCII transliteration of the entity name.

Populated from GLEIF data for entities with non-Latin names (CJK, Cyrillic, Arabic, etc.). Null for entities already in Latin script.

std::nullopt

2.6. party_category

Structural classification of this party.

References the party_category lookup table. Values: 'System' (one per tenant, auto-created during provisioning) or 'Operational' (business entities created during normal system operation). Not user-editable via the UI – every party created through a detail form is 'Operational'; 'System' parties are exclusively auto-created during tenant provisioning. Defaults here so a freshly-constructed party (the Add form, with no UI field for this column) always carries a value the party_category-validation trigger accepts.

std::string("Operational")

2.7. party_type

Classification of this party.

References the party_type lookup table.

std::string("Corporate")

2.8. parent_party_id

Parent party for hierarchy.

References the parent party record for group structures. Null for root parties.

std::nullopt

2.9. business_center_code

Business center location code.

FpML business center code indicating primary location. Synthetic parties default to WRLD (the global sentinel business centre, guaranteed seeded for every tenant by the system-tenant bootstrap – see the same reasoning in ores.refdata.book.org), not a real FpML city code like GBLO: real city codes only exist once the large, optional FpML business-centre reference dataset has been loaded, so using one here would make a freshly-generated party fail its FK-validation trigger in any environment that hasn't loaded it.

std::string("WRLD")

2.10. status

Current lifecycle status.

References the party_status lookup table.

std::string("Active")

2.11. image_id

Optional reference to a logo/branding image in the images table.

std::nullopt

3. Foreign keys

3.1. parent_party_id

3.2. image_id

4. Insert trigger

4.1. Validations

column validation_function
party_category ores_refdata_validate_party_category_fn
party_type ores_refdata_validate_party_type_fn
status ores_refdata_validate_party_status_fn
business_center_code ores_refdata_validate_business_centre_fn

5. SQL

Everything below is consumed by the SQL codegen pipeline only.

5.1. Flags

5.2. Checks

expression
length("codename") <= 32
"codename" = '' or "codename" ~ '^[a-z][a-z_]+$'

5.3. Indexes

name columns unique current_only where_extra
full_name tenant_id, full_name false true  
codename_uniq codename true true codename <> ''
root_party_uniq tenant_id true false parent_party_id is null and party_category <> 'System' and valid_to = ores_utility_infinity_timestamp_fn()
system_party_uniq tenant_id true false party_category = 'System' and valid_to = ores_utility_infinity_timestamp_fn()

5.4. Codename sequence

Sequence used to generate a unique base-26 suffix for auto-generated codenames. nextval() is outside MVCC: it advances even within a multi-row INSERT statement, so every row in a batch gets a different suffix, avoiding duplicate-key collisions that a NOT EXISTS loop cannot prevent due to the statement-level snapshot in READ COMMITTED.

-- Sequence used to generate a unique base-26 suffix for auto-generated
-- codenames.  nextval() is outside MVCC: it advances even within a
-- multi-row INSERT statement, so every row in a batch gets a different
-- suffix, avoiding duplicate-key collisions that a NOT EXISTS loop cannot
-- prevent due to the statement-level snapshot in READ COMMITTED.
create sequence if not exists ores_refdata_party_codename_seq;
drop sequence if exists ores_refdata_party_codename_seq;

5.5. Codename immutability (update path)

Codename is immutable: restore it from the active row on every update, rather than letting a client-supplied value silently overwrite it.

-- Codename is immutable: restore from active row on every update.
select codename into NEW.codename
from "ores_refdata_parties_tbl"
where tenant_id = NEW.tenant_id
  and id = NEW.id
  and valid_to = ores_utility_infinity_timestamp_fn();

5.6. Codename generation and queue provisioning (insert path)

Auto-generates codename (whimsical name + a globally-unique base-26 sequence suffix) when the client leaves it blank, validates the final format, and provisions the per-party report-event queue – all only on first insert, since codename is immutable thereafter (see above).

-- Auto-generate codename using whimsical name + sequence suffix.
-- The sequence suffix is outside MVCC (nextval always advances),
-- so every row in a multi-row INSERT gets a distinct suffix, making
-- the codename globally unique without relying on a NOT EXISTS
-- check that cannot see sibling rows in the same statement.
if NEW.codename = '' or NEW.codename is null then
    NEW.codename := ores_utility_generate_whimsical_name_fn() || '_' ||
        ores_utility_to_base26_fn(nextval('ores_refdata_party_codename_seq'));
end if;
-- Validate the final codename.
if NEW.codename !~ '^[a-z][a-z_]+$' then
    raise exception 'codename must match ^[a-z][a-z_]+$ got: %', NEW.codename
        using errcode = '23514';
end if;
-- Provision the per-party report event queue.
perform ores_mq_queues_create_fn(
    NEW.tenant_id, NEW.id, 'party', 'task',
    'report_events', 'Per-party report scheduling queue', current_user);

6. Messages

The composite_as_of pair, declared here so both the header and its TypeScript twin render it.

6.1. get_party_composite_as_of_request

/**
 * @brief Reads a party as it stood at a specific version, together with its
 * identifiers and contact information as they stood during that same
 * version's [valid_from, valid_to) window. See the "Temporal composite
 * entity versioning" architecture doc.
 */

6.1.1. id

6.1.2. version

6.2. get_party_composite_as_of_response

6.2.1. success

6.2.2. message

6.2.3. party

6.2.4. identifiers

6.2.5. contacts

7. C++

Everything below is consumed by the C++ codegen pipeline only.

7.1. Flags

7.2. Repository

Reads are left to row level security rather than filtered again in the query. IAM resolves a party's name while authenticating – the chooser a caller picks from – and it authenticates as its own service account, which lives in the system tenant. A party row belongs to the tenant that created it, so an app-level filter of tenant_id = the caller admits none of them, and every name in the chooser comes back empty. The policy on this table admits the owning tenant or the system tenant, and that is the statement that should decide it. Every other tenant still sees only its own rows.

7.3. Domain includes

#include <chrono>
#include <string>
#include <optional>
#include <boost/uuid/uuid.hpp>

7.4. Entity includes

#include <string>
#include <optional>
#include "sqlgen/Timestamp.hpp"
#include "sqlgen/PrimaryKey.hpp"

7.5. Conventions

7.6. Table display

column header
short_code Code
full_name Name
party_type Type
status Status
business_center_code Business Center
modified_by Modified By
version Version

7.7. Presentation

7.7.1. Detail fields

field label widget type is_key is_required placeholder flag_source combo_allow_blank combo_fetch combo_domain_type combo_display_field combo_fetch_include combo_fetch_fn combo_watcher_name combo_code_field combo_tooltip_field combo_sort_field combo_label combo_setter_pascal badge_key combo_blank_label
short_code Short Code codeEdit line_edit true true Enter short code                              
full_name Full Name nameEdit line_edit false true Enter full name                              
party_type Party Type partyTypeCombo dynamic_combo false           refdata::domain::party_type     fetch_party_types partyTypeWatcher code description display_order party types PartyTypeCombo party_type  
status Status statusCombo dynamic_combo false           refdata::domain::party_status     fetch_party_statuses partyStatusWatcher code description display_order party statuses PartyStatusCombo party_status  
business_center_code Business Center businessCenterCombo flagged_combo false     business_centre false fetch_business_centre_codes                        
parent_party_id Parent Party parentPartyCombo dynamic_combo false true         refdata::domain::party full_name   fetch_parties parentPartyWatcher id short_code version parties ParentPartyCombo   No Parent

7.7.2. Columns

enum_name field header type width is_badge badge_key formatter formatter_include
ShortCode short_code Code string 120        
FullName full_name Name string 250        
PartyType party_type Type string 120 true party_type    
Status status Status string 100 true party_status    
BusinessCenterCode business_center_code Business Center string 130        
Version version Version int 80        
ModifiedBy modified_by Modified By string 120        
RecordedAt recorded_at Recorded At timestamp 150        

7.7.3. Icon columns

Business center gets its own flag icon, same as any other single-business-centre cell (e.g. Book's rates centre code column).

column accessor field1 field2
BusinessCenterCode business_centre_flag_icon business_center_code  

7.8. Custom repository methods

The two methods below are beyond the standard repository template because they need raw SQL access: read_system_party (calls a stored procedure) and read_descendants (a recursive CTE over the hierarchy). The paginated read_latest(offset, limit) and get_total_party_count() used to be hand-maintained here too, but are now covered by the generic :service_pagination: true facet — removed to avoid duplicate declarations. The codegen mechanism injects the remaining two at template-rendered markers.

7.8.1. read_system_party

Calls ores_refdata_read_system_party_fn stored procedure. Every tenant has exactly one system party (party_category'System'=) which serves as the root of the party hierarchy.

std::vector<domain::party>
read_system_party(context ctx, const std::string& tenant_id);
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
#include "ores.utility/uuid/tenant_id.hpp"
std::vector<domain::party>
party_repository::read_system_party(context ctx, const std::string& tenant_id) {
    BOOST_LOG_SEV(lg(), debug) << "Reading system party for tenant: " << tenant_id;
    const std::string sql =
        "SELECT * FROM ores_refdata_read_system_party_fn('" + tenant_id + "'::uuid)";
    const auto rows = execute_raw_multi_column_query(ctx, sql, lg(),
        "Reading system party by tenant");
    std::vector<domain::party> result;
    result.reserve(rows.size());
    static constexpr std::array required_columns = {0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14};
    for (const auto& row : rows) {
        if (row.size() >= 16 &&
            std::ranges::all_of(required_columns,
                [&row](int i) { return static_cast<bool>(row[i]); })) {
            domain::party p;
            p.id = boost::lexical_cast<boost::uuids::uuid>(*row[0]);
            p.tenant_id = utility::uuid::tenant_id::from_string(*row[1]).value();
            p.version = std::stoi(*row[2]);
            p.full_name = *row[3];
            p.short_code = *row[4];
            p.party_category = *row[5];
            p.party_type = *row[6];
            if (row[7])
                p.parent_party_id = boost::lexical_cast<boost::uuids::uuid>(*row[7]);
            if (row[8])
                p.business_center_code = *row[8];
            p.status = *row[9];
            p.modified_by = *row[10];
            result.push_back(p);
        }
    }
    return result;
}

7.8.2. read_descendants

Traverses the party hierarchy via a recursive CTE starting from root_id. Returns the root plus every descendant (active records only).

std::vector<boost::uuids::uuid>
read_descendants(context ctx, const boost::uuids::uuid& root_id);
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
std::vector<boost::uuids::uuid>
party_repository::read_descendants(context ctx, const boost::uuids::uuid& root_id) {
    BOOST_LOG_SEV(lg(), debug) << "Reading party descendants. Root: " << root_id;
    const auto id_str = boost::uuids::to_string(root_id);
    const std::string sql =
        "WITH RECURSIVE party_tree AS ("
        "  SELECT id FROM ores_refdata_parties_tbl"
        "  WHERE id = '" + id_str + "' AND valid_to = '" + MAX_TIMESTAMP + "'"
        "  UNION ALL"
        "  SELECT p.id FROM ores_refdata_parties_tbl p"
        "  JOIN party_tree pt ON p.parent_party_id = pt.id"
        "  WHERE p.valid_to = '" + MAX_TIMESTAMP + "'"
        ") SELECT id FROM party_tree";
    const auto rows = execute_raw_multi_column_query(ctx, sql, lg(),
        "Reading party descendants");
    std::vector<boost::uuids::uuid> result;
    result.reserve(rows.size());
    for (const auto& row : rows)
        if (!row.empty() && row[0])
            result.push_back(boost::lexical_cast<boost::uuids::uuid>(*row[0]));
    return result;
}

7.9. Custom NATS protocol messages

party declares one message pair beyond the standard CRUD surface and generic facets, in the top-level * Messages section: composite_as_of (reads a party as it stood at a specific version, together with its identifiers and contact information as they stood during that same version's window — see the "Temporal composite entity versioning" architecture doc). The header and its TypeScript twin both render it from there.

#include "ores.refdata.api/domain/party_contact_information.hpp"
#include "ores.refdata.api/domain/party_identifier.hpp"

7.10. Custom NATS handler methods

Hand-written handler method for composite_as_of, dispatching to party_service=/=party_identifier_service=/=party_contact_information_service.

#include "ores.refdata.core/service/party_contact_information_service.hpp"
#include "ores.refdata.core/service/party_identifier_service.hpp"
#include "ores.database/service/tenant_context.hpp"
#include "ores.service/messaging/workflow_helpers.hpp"
#include <chrono>

save's workflow-step-command branch (below) special-cases orchestration commands ahead of the generated standard flow: it bypasses JWT auth and uses the X-Tenant-Id header instead, replays a cached result if the step already completed (idempotency), and reports completion/failure back to the workflow engine rather than replying to the caller directly.

using ores::service::messaging::is_workflow_command;
using ores::service::messaging::extract_workflow_header;
using ores::service::messaging::publish_step_completion;
using ores::service::messaging::check_step_idempotency;
using ores::workflow::messaging::step_id_header;
using ores::workflow::messaging::instance_id_header;
using ores::workflow::messaging::tenant_id_header;

// Workflow step command: bypass JWT auth; use X-Tenant-Id for context.
if (is_workflow_command(msg)) {
    const auto step_id = extract_workflow_header(msg, step_id_header);
    const auto inst_id = extract_workflow_header(msg, instance_id_header);
    const auto tenant_id = extract_workflow_header(msg, tenant_id_header);

    // Idempotency guard: replay cached result if this step already completed.
    if (auto cached = check_step_idempotency(nats_, step_id)) {
        publish_step_completion(nats_,
                                step_id,
                                inst_id,
                                cached->outcome,
                                cached->result_json,
                                cached->error_message,
                                cached->log);
        return;
    }

    auto req = decode<put_party_request>(msg);
    if (!req) {
        publish_step_completion(nats_,
                                step_id,
                                inst_id,
                                ores::workflow::messaging::step_outcome::failed,
                                "",
                                "Failed to decode put_party_request");
        return;
    }
    try {
        using ores::database::service::tenant_context;
        auto wf_ctx = tenant_context::with_tenant(ctx_, tenant_id);
        service::party_service svc(wf_ctx);
        // The service answers the canonical response, whose result
        // states the outcome. A refusal is the step failing.
        const auto resp = svc.put_party(*req);
        if (resp.result.outcome != ores::utility::domain::outcome::ok) {
            publish_step_completion(nats_,
                                    step_id,
                                    inst_id,
                                    ores::workflow::messaging::step_outcome::failed,
                                    "",
                                    resp.result.message);
            return;
        }
        BOOST_LOG_SEV(party_handler_lg(), debug)
            << "Workflow step completed: " << msg.subject;
        publish_step_completion(nats_,
                                step_id,
                                inst_id,
                                ores::workflow::messaging::step_outcome::completed,
                                rfl::json::write(resp),
                                "");
    } catch (const std::exception& e) {
        BOOST_LOG_SEV(party_handler_lg(), error)
            << "Workflow step failed: " << msg.subject << " — " << e.what();
        publish_step_completion(nats_,
                                step_id,
                                inst_id,
                                ores::workflow::messaging::step_outcome::failed,
                                "",
                                e.what());
    }
    return;
}
void composite_as_of(ores::nats::message msg) {
    BOOST_LOG_SEV(party_handler_lg(), debug) << "Handling " << msg.subject;
    auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
    if (!ctx_expected) {
        error_reply(nats_, msg, ctx_expected.error());
        return;
    }
    const auto& ctx = *ctx_expected;
    auto req = decode<get_party_composite_as_of_request>(msg);
    if (!req) {
        BOOST_LOG_SEV(party_handler_lg(), warn) << "Failed to decode: " << msg.subject;
        reply(nats_,
              msg,
              get_party_composite_as_of_response{.success = false,
                                                 .message = "Failed to decode request"});
        return;
    }
    try {
        service::party_service party_svc(ctx);
        const auto version = static_cast<std::uint32_t>(req->version);
        auto current = party_svc.get_party_at_version(req->id, version);
        if (!current) {
            reply(nats_,
                  msg,
                  get_party_composite_as_of_response{.success = false,
                                                     .message =
                                                         "No such party version: " + req->id +
                                                         " v" + std::to_string(version)});
            return;
        }

        // Windows are contiguous by construction: the next version's
        // valid_from is this version's valid_to. If there is no next
        // version, this is the current one — its window is still open,
        // so bound it with a safely-far-future instant instead (see the
        // "Temporal composite entity versioning" architecture doc; the
        // domain object does not surface valid_to directly).
        auto next = party_svc.get_party_at_version(req->id, version + 1);
        const auto window_end =
            next ? next->recorded_at :
                   std::chrono::system_clock::now() + std::chrono::hours(24 * 365 * 100);

        service::party_identifier_service identifier_svc(ctx);
        auto identifiers = identifier_svc.list_party_identifiers_by_party_id_as_of(
            req->id, current->recorded_at, window_end);

        service::party_contact_information_service contact_svc(ctx);
        auto contacts = contact_svc.list_party_contact_informations_by_party_id_as_of(
            req->id, current->recorded_at, window_end);

        BOOST_LOG_SEV(party_handler_lg(), debug) << "Completed " << msg.subject;
        reply(nats_,
              msg,
              get_party_composite_as_of_response{.success = true,
                                                 .party = std::move(*current),
                                                 .identifiers = std::move(identifiers),
                                                 .contacts = std::move(contacts)});
    } catch (const std::exception& e) {
        BOOST_LOG_SEV(party_handler_lg(), error) << msg.subject << " failed: " << e.what();
        reply(nats_,
              msg,
              get_party_composite_as_of_response{.success = false, .message = e.what()});
    }
}

7.11. Custom NATS subscriptions

Hand-crafted subjects wired into the generated party_registrar.cpp via the nats-sub-registrar facet's custom-subscription paste point.

subs.push_back(nats.queue_subscribe(
    get_party_composite_as_of_request::nats_subject, queue_group, [h](ores::nats::message msg) {
        h->composite_as_of(std::move(msg));
    }));

7.12. Cache aux index

The generated party_cache (nats-event-cache facet, cached_by: iam) carries a parent/child aux index alongside the plain tenant/id → party map, so IAM can answer "which parties are visible from this root" without a DB round-trip. See the cache_aux_type flag under * C++ / ** Flags and the facet doc's "Optional aux index" section.

using children_map = immer::map<boost::uuids::uuid, std::vector<boost::uuids::uuid>, key_hash>;
auto children_t = children_map{}.transient();
for (const auto& [id, p] : entries) {
    if (p.parent_party_id) {
        const auto* existing = children_t.find(*p.parent_party_id);
        auto siblings = existing ? *existing : std::vector<boost::uuids::uuid>{};
        siblings.push_back(id);
        children_t.set(*p.parent_party_id, std::move(siblings));
    }
}
auto aux = children_t.persistent();
std::vector<boost::uuids::uuid>
compute_visible_party_ids(const std::string& tenant_id, const boost::uuids::uuid& root_id) const {
    const auto snap = cache_.snapshot(tenant_id);
    if (!snap)
        return {root_id};
    std::vector<boost::uuids::uuid> result;
    std::vector<boost::uuids::uuid> stack{root_id};
    while (!stack.empty()) {
        const auto node = stack.back();
        stack.pop_back();
        result.push_back(node);
        const auto* siblings = snap->aux.find(node);
        if (siblings)
            for (const auto& child : *siblings)
                stack.push_back(child);
    }
    return result;
}

8. See also

Emacs 29.3 (Org mode 9.6.15)