Fix operational-party association bug in tenant provisioning
Table of Contents
This page is a capture in the discarded bucket of the product backlog.
Resolved (deep backlog refinement, 2026-07-11): Capture's own Update section documents root cause and fix; verified stamp_account_party() exists in projects/ores.iam/core/include/ores.iam.core/messaging/account_party_handler.hpp (lines 77, 192, 238), used in place of the generic stamp() in both save paths. filetags include :resolved:.
What
projects/ores.shell/src/app/commands/provision_commands.cpp's
provision_commands::process_tenant, Phase 3
("Associating '<user>' with the operational parties…", around line
397), is supposed to loop over every party with
party_category = "Operational"= returned by
refdata::messaging::get_parties_request and create one
account_party association per party via
iam.v1.account-parties.save. In a real test run
(barclays_system_provision.ores, tenant barclays), log evidence
from ores.iam.service.0.log around 2026-07-03 00:26:04 shows
~20 identical account_party_mapper "Mapping domain entity" trace
lines, all with the exact same party_id — the tenant's
auto-created System Party's UUID
(4996676e-a823-4a30-a17a-08e828a769d2, party_category = "System",
which the if (party.party_category ! "Operational") continue;=
filter should have skipped) — never any of the ~81 distinct
Operational parties that actually exist in that tenant (BARCLAYS PLC
plus ~80 GLEIF-imported subsidiaries).
Net effect: after running the recipe, tenant_admin ends up
associated only with the System Party, never with any real
operational party. Since login auto-selects when an account has
exactly one associated party
(projects/ores.iam/core/include/ores.iam.core/messaging/auth_handler.hpp,
the account_parties.size() = 1= branch), every subsequent login
lands on System Party context regardless of which tenant/party the
user thinks they're using — this presents as a login bug but is
actually this association step silently doing the wrong thing.
Update — root cause found and fixed. The bug was not in
provision_commands.cpp's loop at all (that code was already
correct: distinct party.id per iteration). It was in the shared
ores::service::messaging::stamp() helper
(projects/ores.service/include/ores.service/messaging/handler_helpers.hpp):
it unconditionally overwrites any domain object's party_id field
from the caller's own JWT context, which is correct for entities
where party_id means "which party owns this row" (a security
boundary) but wrong for account_party, where party_id means "which
party is being targeted by this association" — a client-supplied
value. Because account_party happens to have a field literally
named party_id, the generic reflection-based stamp() matched on
name alone and clobbered every association with the caller's own
current party (the System Party) before it reached the database —
account_party_handler.hpp's save() called stamp(ap, ctx) on each
row.
Fixed by adding a narrow stamp_account_party() in
projects/ores.iam/core/include/ores.iam.core/messaging/account_party_handler.hpp
that stamps tenant_id=/=modified_by=/=performed_by=/=change_reason_code
but leaves account_id=/=party_id exactly as supplied by the client,
used in place of the generic stamp() in both the synchronous and
workflow-step save() paths. provision_commands.cpp's Phase 3 loop
was not touched — it was already correct and should now work as
intended.
Why
Discovered while implementing Synthetic data librarian support: FX foundation — a party-scoped synthetic FX config was correctly created in the database, but appeared invisible in the Qt client because the logged-in account was never actually linked to the party being tested, due to this bug.
References
projects/ores.service/include/ores.service/messaging/handler_helpers.hpp,stamp()— the generic helper whoseparty_idoverwrite caused this.projects/ores.iam/core/include/ores.iam.core/messaging/account_party_handler.hpp,stamp_account_party()— the fix.projects/ores.iam/core/include/ores.iam.core/messaging/auth_handler.hpp, theaccount_parties.size() =1= auto-select branch — explains why the symptom looked like a login bug.doc/recipes/shell/provisioning/barclays_system_provision.org— the recipe that reproduces this; should be re-tested against a fresh DB once this fix is verified end to end.
See also
- Synthetic data librarian support: FX foundation — the story during which this was found.