An instrument cannot be saved through the trading service
Table of Contents
This page is a capture in the inbox bucket of the product backlog — a pre-sprint idea, not yet pulled into a sprint as a story.
1. What
Saving an instrument through the trading service fails on the store's audit
check: modified_by cannot be null or empty. Every instrument type fails, and
it is not a data problem.
The generated write structs carry the business fields and no audit.
equity_forward_instrument_write, for instance, holds instrument_id,
trade_type_code, trade_id, the terms, and description – and no
modified_by, performed_by, change_reason_code or change_commentary. The
handler maps the write struct into the domain, so the domain's audit is empty,
and the mapper passes that emptiness to the entity. The insert trigger then
refuses the row.
Nothing stamps it. grep modified_by projects/ores.trading/core/src/messaging/*.cpp
returns nothing: no trading handler sets an audit field, for any entity.
2. Why
The store's validator is explicit that the application must set the field:
ores_iam_validate_account_username_fn raises "Application code must set this
field explicitly" once the tenant has a real account, and it accepts an empty
value only while the tenant is still in bootstrap. Trades survive because the
ORE mapper fills their audit; instruments do not, because the DTO they travel in
has nowhere to put one.
It went unnoticed because instruments are created by the SQL seeds, which set
the audit columns directly and never go through the service. The ORE import is
the first caller to write an instrument through NATS, and it fails on all 46
trades of a sample portfolio – vanilla_swap, equity_option,
equity_forward, cap_floor, swaption, fx_vanilla_option, fx_forward,
bond and credit repositories all report it.
Two coherent fixes, and the choice is architectural.
- Give the write structs an audit and have each handler pass it through. That
is the shape
credit_instrument,commodity_instrument,composite_instrument_dataandscripted_instrumentalready use, where the handler assigns the whole domain object (req.data = r) rather than copying field by field. It is a codegen change and it regenerates the whole trading instrument protocol surface. - Have the handler stamp the audit from the caller it is acting for, which is
what
ores.workflow'sactor_from_messagedoes for a workflow run: verify the forwarded token, take the username, fall back to the service account. This keeps the DTO free of audit, which is the more defensible boundary – the actor is a property of the request, not of the payload – but it puts the stamp in every generated handler.
The second is the better boundary and the first is the smaller change. Neither belongs in a mop-up: the trading instrument protocols are being regenerated by Sync the ores.trading tree to its models, so a change here would collide with it.
3. References
projects/ores.trading/api/include/ores.trading.api/messaging/equity_forward_instrument_protocol.hpp:42— the write struct with no auditprojects/ores.trading/core/src/repository/vanilla_swap_instrument_mapper.cpp:84—r.modified_by = v.audit.modified_by, which passes the emptiness onprojects/ores.sql/create/iam/iam_tenant_functions_create.sql:392— the validator that refuses itprojects/ores.ore/service/src/messaging/ore_import_execute_handler.cpp:1411— the swap branch that copies field by field and never the auditprojects/ores.workflow/core/include/ores.workflow.core/service/workflow_actor.hpp:32—actor_from_message, the existing way to resolve a caller
4. See also
- Object Storage — where the ORE import's blobs travel, which is how the run got this far.
- Service-to-Service Auth Patterns — where the actor of a delegated call is settled.