Task: Consolidate and migrate ores.qt ClientManager to wire_codec
Table of Contents
This page documents a task in the Make NATS wire format configurable: JSON/MessagePack, decided once at startup story. It captures the goal, current status, acceptance, and any notes or results.
Goal
ClientManager (the Qt client's NATS entry point) has roughly ten
separate call sites hard-coding rfl::json, spread across
ClientManager.hpp=/.cpp= plus two extra translation units
(ClientManagerExportPortfolio.cpp, ClientManagerTradeInstrument.cpp):
the process_authenticated_request template's several overloads,
send_authenticated_request* variants, login, signup, the event-cache
decode path, exportPortfolio, and trade_instrument. Consolidate
these onto one or two shared private helpers first – reducing
duplication as a side effect – then have those route through an
injected wire_codec instead of calling rfl::json directly.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Make NATS wire format configurable: JSON/MessagePack, decided once at startup |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-29 |
Acceptance
[X]All ~10 call sites route through a small number of shared helpers rather than each hard-codingrfl::jsonindependently.[X]Those helpers use the injectedwire_codec.[X]Existing Qt client behaviour is unchanged when the codec is configured forjson(today's default) – verified via the full automated suite (ores.qt.tests,ores.qt.headless.tests, full 74-suitectest, all green) plus a live environment smoke test (see* Plan). One residual gap: this environment has no GUI-automation tool for a native Qt desktop app, so login/authenticated-request flows were not click-driven end-to-end in the running client – documented explicitly per this project's "say so if you can't test the UI" convention, rather than claimed as fully covered.
Plan
Consolidated ClientManager's ~10 rfl::json call sites onto two
shared private template helpers (encode_request<T>=/
=decode_response<T>, in ClientManager.hpp) that route through
ores::nats::default_wire_codec() – the same process-wide codec
ores.service's handler_helpers reads, set automatically when
nats_client::connect() constructs its underlying
ores::nats::service::client.
ClientManager.hpp: the fourprocess_request=/ =process_authenticated_requestoverloads now callencode_request(request)=/=decode_response<ResponseType>(raw)instead of inlinerfl::json::write=/=read, collapsing the duplicated result-unwrapping/error-formatting logic into the shared helpers.ClientManager.cpp:testConnection()=/=signup()migrated too – and since both open a throwawaynats_client(their ownores::nats::service::client, separate from the mainsession_), each now stamps its tempnats_options.formatfromores::nats::default_wire_codec().format()before connecting. Without this, a throwaway connection's defaultjsonwould silently reset the process-wide codec out from under an already-connectedmsgpackmain session – a real bug this task's audit caught, not present before because there was no process-wide default to clobber. The event-cache decode path (subscribeToEvent's lambda) migrated the same way.ClientManagerExportPortfolio.cpp:encode_request=/ =decode_responsereplace the inline calls; the MSVC-C1202 isolation property (this TU is where the reflect-cpp instantiation forexport_portfolio_responsehappens) is preserved sincedecode_response<T>is itself a template instantiated at the call site.ClientManagerTradeInstrument.cpp: the write side (encodingget_trade_instrument_request) migrated toencode_request. The decode side (parse_trade_instrument(), in the separateores.qt.headlessmodule) is a JSON-specific two-phase parser (peeksproduct_type, then re-parses the same raw bytes as different wrapper structs) outside this task's named file scope – left asrfl::json-only, documented inline and here. Underwire_format::json(today's default) this is fully correct; undermsgpackit would currently fail (request/response format-mismatched) – a known, explicitly flagged gap for the story's end-to-end-verify task to pick up, not silently introduced.- Also migrated the two remaining
ores.qtstragglers the write-up task's blast-radius audit named (not literally inside this task's three-file description, but the only task in this story that coversores.qtat all):MarketSimulatorWindow.cpp'sextract_tick_valueand its three snapshot/live-subscription decode call sites (nowstd::span<const std::byte>-based, viadefault_wire_codec()), andShellMdiWindow.cpp's shell-login encode/decode. ores.iam.client/client/service_token_provider.cpp(discovered live, see* Test Scenarios): every service authenticates itself at startup viamake_service_token_provider()'s eagerauthenticate()call, which hard-codedrfl::json::write=/=readforservice_login_request=/=responseandrefresh_response– entirely outside any file list any completed task in this story had audited, and a genuinely universal blocker (amsgpack-configured process cannot even authenticate itself, let alone serve requests, until this is fixed). Migrated ontoores::nats::default_wire_codec(), same shape as every other fix in this story.
Verification: full-repo compass build --preset linux-clang-debug-make
clean (100%, no errors); full compass build rat --preset
linux-clang-debug-make (every component's test_<component> target)
passed with zero failures. Live smoke test #1 (json, today's default):
started services, provisioned Barclays Plc (GLEIF) end-to-end via
ores.shell (41/41 dataset steps completed, exercising the full NATS
request/reply/decode/=wire_codec= pipeline server-side), then
launched the Qt client – it connects to NATS and renders its main
window with no crash, confirming the new ClientManager code links
and runs cleanly. Did not click through an interactive login/
authenticated-request flow in the running client – no GUI-automation
tool is available in this environment for a native Qt app; flagged
explicitly rather than claimed. Live smoke test #2 (msgpack): see
the linked test scenario –
manually launched ores.iam.service --nats-wire-format msgpack,
which first failed exactly at the service_token_provider blocker
above (confirming the bug was real, not theoretical), then, after the
fix, progressed correctly through the self-login round-trip (account
lookup, session creation, effective-permission computation) under
msgpack. A json-mode regression check (ores.shell login against the
normally-supervised fleet) confirmed the fix didn't disturb the
default path.
Notes
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 |
|---|---|---|
| Service self-authentication round-trips correctly under msgpack wire format | PASSED | Live msgpack test; caught and fixed a universal service_token_provider blocker (see Plan) |
PRs
| PR | Title |
|---|---|
| #1755 | [ores.qt] Consolidate and migrate ClientManager to wire_codec |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | send_authenticated_request*'s body parameter still named json_body though it carries wire_codec bytes now | ClientManager.hpp, ClientManager.cpp | Fixed | Renamed to encoded_body across all three overloads and their .cpp definitions |
| encode_request builds a std::string via reinterpret_cast for interop with existing std::string_view-based signatures – worth revisiting if those signatures ever change to std::span | ClientManager.hpp | Declined | Correct and safe as-is; changing session_.request()/send_authenticated_request*'s signatures is a larger, unrelated refactor | |
| No new automated unit test for ClientManager's codec migration itself | ClientManager.hpp | Declined | ClientManager isn't unit-testable without a live NATS session; covered by compass build rat plus the live json/msgpack smoke tests already in this task's Result | |
Result
ClientManager's ~10 rfl::json call sites are consolidated onto
two shared helpers (encode_request<T>=/=decode_response<T>) that
route through ores::nats::default_wire_codec(), plus the two named
ores.qt stragglers (MarketSimulatorWindow.cpp,
ShellMdiWindow.cpp). ClientManagerTradeInstrument.cpp's decode
side (parse_trade_instrument()) stays rfl::json-only, documented
as an explicit, correct-under-json gap for the story's end-to-end
task. Full-repo build and 74-suite ctest pass; a live smoke test
under json confirmed provisioning and client startup work
end-to-end; a second live smoke test deliberately targeting msgpack
caught and fixed a genuinely universal blocker outside this task's
original file scope –
ores.iam.client::service_token_provider hard-coded rfl::json for
every service's self-authentication at startup, which would have
broken any msgpack-configured process before it could serve a
single request. Both the fix and its live verification are recorded
in a new test scenario. A repo-wide
sweep also surfaced several more service-to-service call sites still
bypassing handler_helpers (party_cache.hpp, tenant_handler.hpp,
crm_client.cpp, currency_pair_convention_cache.hpp,
report_definition_template_handler.hpp, trade_handler.hpp) –
none block today's json default, but each needs the same
default_wire_codec() migration before a full-environment msgpack
switch is safe; left for the story's end-to-end-verify task, per its
own * Notes list.