Task: Add the fx instrument verb units
Table of Contents
This page documents a task in the Manage trading entities from the shell story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
ores.shell extends the trading command folder to the seven fx instrument families. Each unit (fx_accumulator, fx_asian_forward, fx_barrier_option, fx_digital_option, fx_forward, fx_vanilla_option, fx_variance_swap) keeps the uniform skeleton from the reference-entity task — submenu, paginated get, change-reason arguments on add, history over the history subject — and declares the entity-specific data: trading.v1 subject family, request and response classes from the per-product protocol, save columns from the org model, instrument_id key. This is the first instrument batch under the 2026-09-07 scope decision; it sets the instrument add-verb convention (payload fields from positional arguments mapped to the org columns, identifiers minted client-side where the protocol does not default them) that the equity, rates and generic batches clone. The batch closes with the live fx system test: every unit round-trips add, list, history and delete through the shell against the fleet, and each save is verified at the DB layer and on the changed-event stream.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Manage trading entities from the shell |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Equity batch (task_shell-trading-equity-instrument-verbs). |
| Last touched | 2026-09-07 |
3. Acceptance
- The seven fx units exist under projects/ores.shell/src/app/commands/trading/ and compile; trading_commands registers them; the shell cmake component files regenerate with no drift.
- Live against the fleet: add, list and page, history and delete round-trip every fx family through its trading.v1.fx_* subjects; each save lands a DB row (verified via psql) and emits the changed event.
- Full preset build and ctest green; shell cmake-sources drift clean. The batch lands on feature/shell-trading-instrument-verbs as one [ores.shell] commit.
4. Plan
Each fx unit clones the reference-entity skeleton — plural submenu on
the trading menu, paginated get, change-reason arguments on add,
history over the history subject — and declares only its
entity-specific data: trading.v1. subject family, request and
response classes from the per-product protocol, save columns from the
org model, instrument_id key. Because the seven units differ only in
that declared data, one throwaway generator (/tmp/fxgen/gen_fx_units.py,
a python string.Template over a PRODUCTS table) emitted all seven;
the emitted files land checked-in as the hand-written surface until
codegen grows an instrument facet. The generator is the convention the
equity, rates and generic batches clone.
Two conventions were fixed here for every instrument batch. First,
add mints the instrument_id client-side (random generator) where the
protocol does not default it, and reads the party from the session
principal's default_party_id. Second, every add stamps
v.audit.modified_by = session.auth().username: the instrument insert
triggers call ores_iam_validate_account_username_fn on
NEW.modified_by, which rejects empty names and requires a match in
ores_iam_accounts_tbl once the tenant has non-service accounts, and
the trading service passes the client's domain object through
unstamped.
Display went in alongside the verbs: each of the seven modeling orgs
gained a Table display section and its renderer in the per-product
*_table.cpp prints the fixed column set. Org and table cpp changes
ride in the same commit as the units so the batch stays one coherent
surface.
The live test is the point of the batch: scripted shell runs per
family round-trip add, get, history and delete against the fleet, with
each save verified twice — a DB row under the acme tenant
(12baaab3-55f1-47fb-a409-2afde4ac42b9) via psql as the ddl user (the
shell cli role is RLS-scoped and reads nothing), and a changed event
captured by a wildcard nats subscriber on
ores.dev.brave_hopper.ores.trading.>. Delete is a bitemporal soft
close (valid_to stamped), and emits a second changed event. Login
uses tenant_admin@acme: the .env principal
(tenant_admin@acme_corporation) predates the 2026-09-05
re-provision and its hostname resolves to no tenant.
Close: cmake component-file drift check, full preset build + ctest,
then the batch lands as one [ores.shell] commit on
feature/shell-trading-instrument-verbs.
5. Notes
6. Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
7. PRs
| PR | Title |
|---|---|
| #2033 | [ores.shell,ores.trading] Manage trading entities from the shell |
8. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
9. Result
Shipped the seven fx instrument verb units under
projects/ores.shell/src/app/commands/trading/
(fx_accumulator_instrument_commands.{hpp,cpp} through
fx_variance_swap_instrument_commands.{hpp,cpp}), each cloning the
reference-entity skeleton with its own trading.v1 subject family and
positional add arguments from the org columns. trading_commands.cpp
registers the seven plural menus; component_files.cmake lists the
units. Each modeling org gained a Table display section and its
*_table.cpp prints the fixed column set. The batch is one
[ores.shell] commit, 3997afdcbb, on
feature/shell-trading-instrument-verbs.
Live acceptance met on brave_hopper against the fleet. A scripted run
per family logged in as tenant_admin@acme (the .env principal
tenant_admin@acme_corporation is stale: the 2026-09-05
re-provision gave Acme Corp the hostname acme, and the login
principal's hostname must resolve to a provisioned tenant) and
round-tripped add, get, history and delete for all seven families.
Every add landed a DB row under the acme tenant
(12baaab3-55f1-47fb-a409-2afde4ac42b9) with modified_by =
tenant_admin, version 1, and emitted the product changed event
(ores.dev.brave_hopper.ores.trading.fx_*_instrument_changed);
delete soft-closed the row (bitemporal valid_to stamp) and emitted a
second changed event. 16 changed events captured across the two
subscriber sessions cover every family's add and delete. A closing
ddl-user psql sweep found all seven tables at zero live rows, with
only the soft-deleted history rows remaining (fx_digital_option 2,
one per other family).
The live test found one real unit defect, which is the point of the
system test: the instrument insert triggers call
ores_iam_validate_account_username_fn on NEW.modified_by, which
rejects empty names and requires a match in ores_iam_accounts_tbl
once the tenant has non-service accounts, and the trading service
passes the client's domain object through unstamped. Fixed by stamping
v.audit.modified_by = session.auth().username in every add path
(3997afdcbb). A second, environment-side finding: the variance-swap
add test initially used moment_type variance, which the table
check constraint rejects (only Variance=/=Volatility, case
sensitive). Cosmetic: doubles render in scientific notation in the
tables (e.g. 1e+06); the instrument uuid renders raw.
Verification: cmake-sources drift clean
(regenerate_cmake_component_files.py --all --check); full preset
build green; ctest 71/71 passed.
Status at close: Batch committed (3997afdcbb); full preset build and ctest 71/71 green.