Investigation: what ores.ore should own
Table of Contents
A point-in-time record, written on 2026-09-26, of a question the design and documentation survey raised but could not settle. The question is not how to tidy ores.ore; it is what ores.ore is for.
The measurements below are of the component as it stood that day, and one of the four jobs it counts has since been cut back. Read it for the reasoning about scope and ownership; read the component models for what the component is.
1. The stated intent
The owner's model, given while reviewing the survey:
ores.oreshould not be exposing NATS. It should just be a library to provide mappings from and to the ORE XML representation.
That is a narrow, testable definition. A library that maps ORE representations to ORES domain types and back needs: the ORE bindings, the mappers, the readers that turn ORE bytes into those bindings, and the writers that go the other way. It does not need a database, a message bus, an executable, or a shell command surface.
The rest of this page measures how far the component is from that definition, so the gap can be closed deliberately rather than by attrition.
2. What the component owns today
ores.ore is a composite of three parts, api, core and service, plus a
root modeling/ directory that holds the one entity model. Every C++ line in
the component falls into one of four jobs:
| Job | Facets | Lines | Is it ORE representation? |
|---|---|---|---|
| A. ORE representation | core/domain minus the bindings, core/xml, core/log |
11,318 hand-written | Yes |
— the xsdcpp bindings |
core/domain/domain.{hpp,cpp}, domain_xsd.hpp |
56,645 generated | Yes |
| B. ORE market data readers | core/market |
1,105, of which 639 read and 466 do not | Mostly |
| C. Import orchestration | core/planner, core/hierarchy, core/scanner, the import protocols in api, service/app, service/config, the import handlers in service/messaging |
4,461 | No |
D. The series_key_shape entity |
api/domain, core/repository |
~1,400 plus SQL | No, but read in process |
Job A is the component's reason to exist and is in scope by the stated intent. The other three are what the component does besides map ORE representations.
Job B, the market data readers, in more detail. This facet holds two
different things. market_data_parser (free functions parse_market_data and
parse_fixings) and fixing read ORE's -market*.txt and fixings*.txt
text formats. That is representation work and belongs in scope. series_key_registry
and fx_quote_convention_checker are not: the first decomposes an oresmd key
using rows from a database table, and the second validates a currency pair
against ores.refdata conventions. They are ORES logic that happens to be
about ORE files.
Job C, import orchestration. ore_import_planner decides what an import
will do before anything is written: which currencies to import and how
(create, update, skip), whether a re-import replaces or merges, and what
defaults a trade gets. ore_hierarchy_builder turns a flat scan into a
portfolio and book tree. ore_directory_scanner classifies files. Around them
sit the ore.import protocol, the object-storage tarball helper, and the
workflow registration. None of this maps a representation; all of it decides
policy and drives a saga. It is a service.
Job D, the series_key_shape entity. This was the component's largest
unjustified surface when this page was written: a full CRUD stack for one
entity, with a 35-class NATS protocol, change events, a sample generator, a
presentation renderer, a service layer and a shell command unit. It has since
been cut back to what is used. The entity's model page justifies the table
belonging to ores.ore, because the key grammar is ORE's and a table lets ORE
add a type later without a rebuild, and that argument covers the data and the
repository that reads it. It never covered the surface, and nothing served the
surface: the registrars were composed nowhere and the shell unit was never in
the build. The protocol, the events, the generator, the presentation mapper,
the service layer, the shell command and the TypeScript twins are now disabled
in the model, leaving an entity, a repository and their SQL. What remains is
still not ORE representation, and the ownership question below is unchanged: a
reference table one component reads in process could live anywhere.
Job D was unwired. Of the four registrar families the regeneration
produced, three were declared, defined, compiled and called from nowhere, so
the nine ore.v1.series_key_shapes* subjects answered nothing and the generated
shell command unit was not compiled because it landed in a directory no CMake
target owned. All of it is now removed rather than wired: the surface served
nobody, and a reference table an operator does not edit needs no commands.
3. The three external use cases
Fourteen includes reach ores.ore.core from outside and three reach
ores.ore.api. They fall into three use cases, and they are not equally
defensible.
1. ores.marketdata — seven files, the substantive case.
| File | What it reaches for |
|---|---|
core/src/service/import_service.cpp |
parse_market_data, parse_fixings, fixing, series_key_registry, fx_quote_convention_checker, series_key_shape_repository |
core/include/…/oresmd/oresmd_projections.hpp |
fx_quote_convention_checker |
service/src/app/feed_ingest_loop.cpp |
series_key_registry, series_key_shape_repository |
| two test files | the parser and the fx checker |
This is the honest use of the component: ores.marketdata imports ORE market
data files, and the only code that knows ORE's text formats is ores.ore. Two
of the six things it reaches for are the leak. It builds the key registry from
series_key_shape_repository{}.read_latest(ctx), a direct read of another
component's table, and it checks FX quotes against ores.refdata through a
type that lives in ores.ore.
2. ores.compute — one file, and clean. wrapper/src/app/log_publisher.cpp
calls parse_ore_log_line(line) and gets an ore_log_line struct. That is 226
lines of pure format parsing with no state, no database and no bus. It is
exactly what the stated intent describes, and it should stay.
3. ores.shell — two files, and both are avoidable.
application/…/trading/ore_commands.cpp serialises planner::import_choices
to JSON for an ore.import request, and calls exporter::export_portfolio on
the items that come back from a trading NATS reply. The shell is a client: it
should ask over the protocol, not link a library. The second file,
ore/…/series_key_shape_commands.cpp, does use the generated protocol, and is
not compiled.
Note what the shell does not do: it drives the import through
workflow.v1.ore.import. Job C is already server-side. Only the small type
that parameterises the request, and the export step on the reply, are on the
client.
4. What follows from the stated intent
Three consequences, in order of confidence.
The entity (Job D) does not belong in this component. The owner intent and
the entity's own justification point the same way: the grammar is ORE's, but the
entity is an oresmd series key shape, and oresmd is ores.marketdata's
vocabulary. ores.marketdata already models market_series,
market_observation, feed_binding and the oresmd/quote_type models, and it
is the only real consumer of the table. Cutting the surface back has already
removed the component's NATS exposure, its shell unit and its events; what a
move would still buy is the entity and its repository, its SQL schema, and its
key registry. It would also cost a full ores.marketdata regeneration: that
component is not in COMPONENTS_UNDER_TEST, so its committed output predates
the current templates, and adding one model to it materialises 135 files, of
which about thirty are the entity's.
Import orchestration (Job C) is a service and should be named as one. Either
a slimmed =ores.ore.service keeps it, in which case the component is a library
plus a service and the api holds only the import contract, or it moves to its own
component and ores.ore becomes a pure library with no executable at all. The
first is a smaller change and keeps the ores.ore.service registry entry,
database role and account. The second matches the stated intent exactly.
Two pieces of Job B are in the wrong place within the component.
series_key_registry should follow the entity to ores.marketdata, since it
exists to decompose keys using that entity's rows. fx_quote_convention_checker
either follows it or moves to the shared contract, because its input is
ores.refdata conventions rather than ORE.
5. Options
| Option | What moves | Effect on the component |
|---|---|---|
| 0. Remove the unwired surface (done) | The entity's protocol, events, generator, presentation mapper, service layer, shell command, recipe and TypeScript twins | Already landed. The component exposes six subjects and serves all six; the api is a third smaller. Does not move the entity, so the ownership question stays open. |
| 1. Record the remaining shape | Nothing further | ores.ore stays a library, a service and the entity's table. Cheapest, and the survey's finding 2 is answered with a reason rather than a change. |
2. Move the entity to ores.marketdata |
The entity and its repository and SQL, plus series_key_registry |
Puts the table next to the only code that reads it. Costs that component's whole regenerated backlog in the same branch: 135 files, about thirty of them the entity's. |
| 3. Move the entity, and move the saga out | Jobs C and D | ores.ore becomes core only: a pure library with bindings, mappers and readers. The strongest match to the stated intent, and the largest change: a new component, a new service registry entry, and the shell's export step must move onto a protocol. |
Options 2 and 3 both require the shell to stop calling exporter::export_portfolio
directly. That is a small, independent piece of work and can be done first.
6. Recommendation
Option 0 is taken: the surface was the part of this component that no evidence supported, and removing it closes the survey's P03, W01 and D13 findings outright. The remaining question is smaller than it was and no longer blocks the clean standard, which is why it is recorded rather than forced.
Between options 1 and 2, the argument for 2 is that the entity is
ores.marketdata's vocabulary and it is the only consumer. The argument for 1
is the cost: 135 files of another component's backlog would ride along in the
same branch, none of it this component's to regenerate, and the entity's own
contribution to that number is about a quarter of it. That coupling is the real
obstacle, and it is worth clearing first: bring ores.marketdata to a
regenerable state in its own task, then move the entity in one clean step.
Until then the table is documented as living here deliberately.
Option 3 is unaffected by any of this and remains the target if ores.ore is to
stop hosting a service at all.
7. See also
- Investigation: ores.ore design and documentation survey — the component census this page builds on.
- ores.marketdata.api — the component the series key shape entity would join.
- ORE Model Configuration — the model configuration domain surface, which the survey also found partly landed.
- Component Clean Standard — the standard this investigation serves.