Task: Add the ORE import and export verbs to the shell
Table of Contents
This page documents a task in the Redesign ores.trading on data-oriented principles story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
The shell can upload an ORE import tarball, start the import workflow, and export a portfolio back to XML on disk, so a script can drive the whole XML to database to XML path through the service layer.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Redesign ores.trading on data-oriented principles |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-09-11 |
3. Acceptance
ores.shellcan upload a file to theore-importsbucket.ores.shellcan start the ORE import workflow for an uploaded tarball and report the outcome.ores.shellcan request a portfolio export and write the reconstructed XML to a file.- The three verbs drive the existing services. No new service, subject or protocol member is needed.
4. Plan
The XML to database to XML path already exists end to end in the services. It has no shell verb at any of its three steps, so this task adds the verbs and nothing else.
| Step | Service | Subject |
|---|---|---|
| Upload the tarball | object storage, bucket ore-imports, key {request_id}.tar.gz |
ores::ore::net::ore_storage |
| XML into the database | ore_import_workflow step 0 |
workflow.v1.ore.import |
| Database back to XML | export_portfolio_request, then exporter::export_portfolio(items) |
trading.v1.trades.portfolio.export |
Each verb follows the shape of the existing shell command units under
projects/ores.shell/src/app/commands/, and each registers in
trading_commands.cpp or the workflow menu.
5. Notes
5.1. The export verb needs the ORE core library
trading.v1.trades.portfolio.export returns items, a vector of
trade_export_item (a trade, its instrument and an optional envelope).
The XML is not on the wire. The shell must map those items into the
ores.ore container and call exporter::export_portfolio itself.
ores.shell links ores.trading.api.lib and ores.workflow.api.lib
today, and it does not link ores.ore.core.lib, so this verb adds that
one link. It adds no service, subject or protocol member, so the
acceptance holds as written.
5.2. Where each verb registers
projects/ores.shell/src/app/repl.cpp builds the root menu and calls
each group's register_commands. workflow_commands takes the root
menu and the session; the trading groups also take the pagination
context. A command unit makes a cli::Menu, inserts its verbs, and
inserts that menu into the root.
The import verb belongs with the workflow group, because it starts a
workflow instance and the group already owns steps and wait, which
the round trip script needs to observe the outcome. The upload and
export verbs belong with the trading group.
5.3. The three subjects
| Step | Subject |
|---|---|
| Upload | ores::ore::net::ore_storage, bucket ore-imports, key {request_id}.tar.gz |
| Import | workflow.v1.ore.import |
| Export | trading.v1.trades.portfolio.export |
5.4. The upload is HTTP, and the shell has no HTTP client
Object storage has no NATS subject. ores.storage is an HTTP client
library (net/http_client.hpp, net/storage_paths.hpp,
net/storage_transfer.hpp), and the two NATS subjects that name storage
(marketdata.v1.series.export-to-storage and
trading.v1.trades.export-to-storage) move data from the server into
storage, which is the opposite direction.
projects/ores.qt/trading/src/OreImportWizard.cpp is the working
reference for the first two steps:
ores::storage::net::storage_transfer transfer(http_base_url);
transfer.pack_and_upload(ore_dir,
std::string(ores::ore::net::ore_storage::bucket),
ores::ore::net::ore_storage::import_key(request_id));
...
ores::ore::messaging::ore_import_request req;
req.request_id = request_id;
req.correlation_id = request_id;
cm->process_authenticated_request(req, std::chrono::minutes(10));
Correction, after writing the verb: ores.shell does hold an HTTP base
URL. compute_commands.cpp defines default_http_base_url() from
ORES_HTTP_PORT and calls storage_transfer at three sites. The
upload verb therefore adds no configuration, only the link.
The three verbs need three new links: ores.storage and ores.ore.api
for the upload and the import request, and ores.ore.core for the
exporter, on top of the ores.trading.api and ores.workflow.api
links already present.
storage_transfer offers both shapes. pack_and_upload(src_dir, ...)
takes a directory and does the packing itself, and upload(bucket, key,
src_file) takes one file. The second fits the acceptance as written,
which has the script tar and gzip the document first, but it puts tar
and gzip in the ores script language. The first avoids that by
pointing the verb at a directory holding the one example document.
Settle this when the verb is written.
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 |
|---|---|
8. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
9. Result
The three verbs shipped in one ore menu: upload, import and
export. They register from trading_commands.cpp and drive the
existing services unchanged.
Acceptance, item by item.
- ores.shell can upload a file to the ore-imports bucket. Met.
ore upload <src_dir> [--request-id <uuid>]callsstorage_transfer::pack_and_uploadagainst bucketore-importsand key{request_id}.tar.gz, the same two callsOreImportWizard.cppmakes. - ores.shell can start the ORE import workflow for an uploaded tarball
and report the outcome. Met.
ore import <request_id> [--party-id] [--choices-file] [--timeout]sends the request onworkflow.v1.ore.importand then waits for the instance it names. The wait is the point. The handler replies with the workflow instance id before the work runs, and a.oresscript cannot capture it, so the verb waits itself and reports the terminal step state. - ores.shell can request a portfolio export and write the reconstructed
XML to a file. Met.
ore export <output_file> [--node-id] [--limit]sendsexport_portfolio_requestontrading.v1.trades.portfolio.export, maps the returned items throughores::ore::xml::exporter::export_portfolioand writes the file. - The three verbs drive the existing services. No new service, subject or protocol member is needed. Met. The diff adds two files and changes three, and no subject, protocol struct or handler.
Design questions the Notes left open, settled.
pack_and_upload over upload(bucket, key, src_file). The second fits
a script that tars and gzips first, which puts tar and gzip into the
.ores language. The first keeps them out and matches the wizard.
One menu, not two. The Notes put the import verb with the workflow group
because the script needed workflow steps and workflow wait to see
the outcome. That reason dissolves once the verb waits itself, so all
three live together under ore and register from
trading_commands.cpp.
One claim in the Notes was wrong and is corrected in place there.
ores.shell does hold an HTTP base URL: compute_commands.cpp defines
default_http_base_url() from ORES_HTTP_PORT and calls
storage_transfer at three sites. The upload verb still adds the
ores.storage link, because nothing else exposes that library to this
translation unit.
The request id is an input, not an output. The script language has no
output capture, so upload takes an optional --request-id and prints
the id it used, and the round trip script supplies the same value to
import from the environment.
Verified on linux-clang-debug-make. The build is clean, ores.shell.tests
passes 1 of 1 in 24.68 sec, and the component_files.cmake drift check
reports every file up to date. The published binary answers offline:
help lists the ore menu, and each verb's usage and validation paths
answer as written, including the directory guard on upload, the
positive-seconds check on import and the unsigned-integer check on
export.
Not verified here: no live run against a serving environment. That is the full round trip task's job, which this task unblocks.