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 in 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

2.12. is_registration_default

Whether a self-registered account in this tenant joins this party. At most one live party per tenant may hold it.

The flag sits on the party row rather than on the tenant because this table is tenant-isolated: a tenant administrator may write their own parties, so the choice is theirs to make. A default_party_id column on the tenant row would be unreachable to them, because only a system-tenant session may write a tenant row.

false

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_]+_[0-9]{4}$'

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()
registration_default_party_uniq tenant_id true false is_registration_default and valid_to = ores_utility_infinity_timestamp_fn()

5.4. Codename sequence

Sequence used to generate a unique auto-generated codename. nextval() is outside MVCC: it advances even within a multi-row INSERT statement, so every row in a batch gets a different value, avoiding duplicate-key collisions that a NOT EXISTS loop cannot prevent due to the statement-level snapshot in READ COMMITTED. ores_utility_whimsical_codename_fn turns each value into a whimsical name and a four-digit number, such as silent_meadow_4217, so two different values never give the same codename.

-- Sequence used to generate a unique auto-generated codename.  nextval() is
-- outside MVCC: it advances even within a multi-row INSERT statement, so every
-- row in a batch gets a different value, 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 (a whimsical name and a four-digit number, from a globally-unique sequence value) 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 the codename from a sequence value: a whimsical name
-- and a four-digit number. The sequence is outside MVCC (nextval
-- always advances), so every row in a multi-row INSERT gets a
-- distinct value, and distinct values give distinct codenames
-- without a NOT EXISTS check that cannot see sibling rows.
if NEW.codename = '' or NEW.codename is null then
    NEW.codename := ores_utility_whimsical_codename_fn(
        nextval('ores_refdata_party_codename_seq'));
end if;
-- Validate the final codename.
if NEW.codename !~ '^[a-z][a-z_]+_[0-9]{4}$' then
    raise exception 'codename must match ^[a-z][a-z_]+_[0-9]{4}$ 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

6.3. put_party_composite_request

/**
 * @brief Writes a party and the children a confirmed review staged, in one
 * transaction. A refusal or a failure leaves none of them behind.
 */

6.3.1. intent

6.3.2. party

6.3.3. identifiers

6.3.4. contacts

6.4. put_party_composite_response

6.4.1. result

6.4.2. party

7. C++

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

7.1. Flags

7.2. Repository

Reads are scoped to the caller's tenant, as for every tenant-scoped entity, the system tenant included. IAM's party cache, which names parties in the sign-in chooser, reads each tenant's partition inside that tenant with a token it issues itself, so it needs no wider read.

The scope sits in the code as well as in the policy: the list read and the count name the caller's tenant in the query. A read that must cross tenants therefore changes the query and the policy, not the policy alone.

7.3. Domain includes

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

7.4. Entity includes

#include <string>
#include <optional>

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 <array>
#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 = query_builder::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 = query_builder::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.8.3. read_registration_default

Reads the party flagged is_registration_default in the caller's tenant, when one is set. At most one live party per tenant may hold the flag, so this is one row or none. A registration into a tenant with no flagged party creates a pending account rather than failing.

std::optional<domain::party> read_registration_default(context ctx);
std::optional<domain::party>
party_repository::read_registration_default(context ctx) {
    static constexpr auto max = db_timestamp::max();
    const auto tid = ctx.tenant_id().to_string();
    const auto query = query_builder::read<std::vector<party_entity>> |
                       query_builder::where("tenant_id"_c == tid && "is_registration_default"_c == true &&
                             "valid_to"_c == max) |
                       query_builder::limit(1);

    const auto rows = query_builder::execute_read_query<party_entity, domain::party>(
        ctx,
        query,
        [](const auto& entities) { return party_mapper::map(entities); },
        lg(),
        "Reading the registration default party.");
    if (rows.empty())
        return std::nullopt;
    return rows.front();
}

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 "ores.utility/codec/wire_traits_fwd.hpp"
#include <boost/uuid/uuid_io.hpp>
#include <chrono>
#include "ores.database/repository/unit_of_work.hpp"

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, tenant_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,
                                ores::utility::codec::wire_traits<put_party_response>::write_json(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;
    if (!has_permission(ctx, "refdata::parties:read")) {
        error_reply(nats_, msg, ores::service::error_code::forbidden);
        return;
    }
    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: " +
                                                         boost::uuids::to_string(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(
            boost::uuids::to_string(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(
            boost::uuids::to_string(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()});
    }
}

void put_composite(ores::nats::message msg) {
    BOOST_LOG_SEV(party_handler_lg(), debug) << "Handling " << msg.subject;
    auto req_ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
    if (!req_ctx_expected) {
        error_reply(nats_, msg, req_ctx_expected.error());
        return;
    }
    const auto& req_ctx = *req_ctx_expected;
    if (!has_permission(req_ctx, "refdata::parties:write")) {
        error_reply(nats_, msg, ores::service::error_code::forbidden);
        return;
    }
    auto req = decode<put_party_composite_request>(msg);
    if (!req) {
        BOOST_LOG_SEV(party_handler_lg(), warn) << "Failed to decode: " << msg.subject;
        error_reply(nats_, msg, ores::service::error_code::bad_request);
        return;
    }
    try {
        // One transaction over every row the confirm staged. The unit of
        // work rolls back when it is destroyed uncommitted, so a refusal
        // anywhere leaves none of the writes behind. Each service is built
        // on the unit of work's context, which is what makes its repository
        // calls join the transaction rather than open their own.
        ores::database::repository::unit_of_work uow(req_ctx);
        const auto& uctx = uow.ctx();
        const auto reason =
            req->intent.reason_code.empty() ?
                std::string(ores::service::messaging::change_reasons::new_record) :
                req->intent.reason_code;
        const auto apply_intent = [&](auto& row) {
            if (row.change_reason_code.empty())
                row.change_reason_code = reason;
            row.change_commentary = req->intent.commentary;
        };

        auto party = req->party;
        apply_intent(party);
        service::party_service parties(uctx);
        parties.save_party(party);

        auto identifiers = req->identifiers;
        for (auto& row : identifiers)
            apply_intent(row);
        service::party_identifier_service identifier_service(uctx);
        identifier_service.save_party_identifiers(identifiers);

        auto contacts = req->contacts;
        for (auto& row : contacts)
            apply_intent(row);
        service::party_contact_information_service contact_service(uctx);
        contact_service.save_party_contact_informations(contacts);

        uow.commit();

        // Read the row back from the committed transaction, so the reply
        // carries the version and the audit stamps the store gave it.
        service::party_service reader(req_ctx);
        auto written = reader.find_party(party.id);
        BOOST_LOG_SEV(party_handler_lg(), debug) << "Completed " << msg.subject;
        reply(nats_,
              msg,
              put_party_composite_response{.result = {},
                                           .party = written ? std::move(*written) :
                                                              std::move(party)});
    } catch (const std::exception& e) {
        BOOST_LOG_SEV(party_handler_lg(), error) << msg.subject << " failed: " << e.what();
        put_party_composite_response failure;
        failure.result.outcome = ores::utility::domain::outcome::failed;
        failure.result.code = "internal_error";
        failure.result.message = e.what();
        reply(nats_, msg, failure);
    }
}

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, party_queue_group, [h](ores::nats::message msg) {
        h->composite_as_of(std::move(msg));
    }));
subs.push_back(nats.queue_subscribe(
    put_party_composite_request::nats_subject, party_queue_group, [h](ores::nats::message msg) {
        h->put_composite(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)