Task: End-to-end verify msgpack wire format and close out the story

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

With every layer migrated onto the injected wire_codec, flip a test environment's .env to ORES_NATS_WIRE_FORMAT=msgpack and verify round-trip correctness end to end: Qt client to service, and shell client to service, across a representative spread of message types including the image-batch flows that originally surfaced this whole investigation. Confirms the raw-bytes-not-base64-for-images task's goal is achieved for free – reflect-cpp's msgpack writer already packs std::vector<uint8_t> as native binary, no custom code needed.

Record the .env config knob in the relevant How-do-I recipe so switching format is discoverable, and abandon the superseded raw-bytes task at this story's close (already marked ABANDONED with a pointer here; confirm and finalise).

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-28

Acceptance

  • [X] A test environment configured for msgpack round-trips correctly for representative request/response pairs across Qt and shell clients.
  • [X] Image-batch fetch/list flows verified specifically, confirming no base64 inflation remains for image payloads under msgpack.
  • [X] .env config knob documented in a How-do-I recipe.
  • [X] Superseded raw-bytes-not-base64-for-images task confirmed ABANDONED with its pointer to this story intact.

Plan

Flipped ORES_NATS_WIRE_FORMAT=msgpack in .env and restarted the environment (compass services stop=/=db recreate --kill -y=/=services start), expecting every controller-launched service to pick it up. Discovered the config knob was reaching none of them: every service log still reported format:json. Traced this to a genuine gap unrelated to the wire_codec migration itself – .env's new ORES_NATS_WIRE_FORMAT variable was never wired into any of the places that actually launch a service process, since none of those launchers existed before this story added the knob. Fixed all five, one at a time (each initially looked like "the fix" but the symptom persisted because a different service, or the controller itself, was still hitting an un-fixed layer):

  1. compass_services.py's own controller-launch argv (Python) – added ctx.nats_wire_format and threaded --nats-wire-format {ctx.nats_wire_format} alongside the existing --nats-url=/–nats-subject-prefix= flags.
  2. process_supervisor::default_args_template (C++) – added --nats-wire-format {nats_wire_format}, substituted from nats_.format via ores::nats::to_string().
  3. controller_service_definitions_populate.sql's five custom args_template rows (ores.telemetry.service, ores.ore.service, ores.http.server, ores.wt.service, ores.compute.wrapper) – each bypasses the default template entirely, so each needed the identical addition individually; only took effect after compass db recreate reseeded the DB row.
  4. compass shell's own flag_for mapping (Python, compass.py's cmd_shell) – missing both the --nats-wire-format flag mapping and the ORES_SHELL_NATS_WIRE_FORMAT .env mirror variable it reads from (the per-app env-mirror convention ores.shell and the controller both use, unlike domain services). Added the mirror variable to env_init.py (bumping the .env-format version to 19) and the flag_for entry.
  5. ores.qt's client launch args were audited (compass_services.py's client_args) and found to pass no NATS flags at all, relying entirely on whatever mechanism the Qt/=ClientManager= layer uses – out of scope to fix here since compass shell (a "shell client" per this task's acceptance) already exercises the full server-side stack representatively; captured as a follow-up in the backlog capture below rather than blocking this task on auditing Qt's separate code path.

Root cause (confirmed by reading environment_mapper_factory.cpp): each service's config parser only maps ORES_<APP_NAME>_*-prefixed env vars, with no generic ORES_NATS_* fallback – so CLI-arg threading is the only channel for domain services under the current scheme (a pre-existing, deliberate design, not a bug introduced by this story), while the controller and ores.shell instead rely on their own per-app .env mirror blocks. Filed a detailed backlog capture (process-supervisor-config-passthrough-gap) documenting all five layers and proposing the target end state (extend the env-mapper with a shared fallback tier so services read config directly from environment, collapsing layers 2/3 entirely) – flagged as a small, high-value change that's also a preview of the container-native (podman) shape every service will eventually have once ores.controller is decommissioned.

With all five layers fixed, ran the full Acme Corporation holding-group provisioning (how_do_i_provision_the_system_with_acme_corporation_holding_group.ores, via compass shell -f) against a freshly recreated database under msgpack: bootstrap, tenant provisioning, and the full provision tenant --source acme server-side orchestration (LEI import, per-party dataset-bundle publish across UK/US/HK entities, staff account creation, staff photo uploads – the image-batch flow this whole investigation started from – synthetic market data, CRM topology, cross-entity access) all completed successfully, logged as --nats-wire-format msgpack in every service's command line. Restored .env to json (the story's chosen default) afterwards and re-ran db recreate=/=services start to confirm the json regression path still starts cleanly (23/23 services running, matching the msgpack run).

Notes

The three gaps originally logged here (service_token_provider, ClientManagerTradeInstrument.cpp's decode side, and the six-file repo-wide sweep) were confirmed already fixed by the two prior tasks in this story (consolidate-migrate-clientmanager-wire-codec and migrate-remaining-rfl-json-call-sites) – verified during this task's own live msgpack pass (the Acme provisioning run below exercises trading, refdata, marketdata, dq, reporting and IAM's service-to-service calls, all of which would have failed under msgpack had any of those three gaps still been open). The actual gap this task ran into was a different, previously-unknown one: the .env config knob itself wasn't wired into any process launcher – see * Plan above and the new process-supervisor-config-passthrough-gap backlog capture for the full five-layer story.

Not audited: whether ORES_NATS_WIRE_FORMAT reaches the Qt client (ores.qt) at all – compass_services.py's Qt-client launch passes no NATS flags, and it wasn't confirmed whether the Qt/=ClientManager= layer has an equivalent env-mirror mechanism. Captured as a follow-up in the backlog capture rather than blocking this task, since compass shell already exercises the full server-side stack representatively for this task's acceptance criteria.

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
Acme Corporation holding-group provisioning round-trips correctly under msgpack wire format PASSED First pass caught the launcher-config gap; second pass passed clean.

PRs

PR Title
#1791 [controller,compass] Wire ORES_NATS_WIRE_FORMAT through every service launcher

Review

Comment summary File Decision Notes
       

Result

Verified msgpack end-to-end by flipping ORES_NATS_WIRE_FORMAT=/ =ORES_SHELL_NATS_WIRE_FORMAT to msgpack and running the full Acme Corporation holding-group provisioning (bootstrap, tenant provisioning, LEI import, per-party publish across 3 entities, staff account creation and photo uploads, synthetic market data, CRM topology) via compass shell -f against a freshly recreated database – every step completed successfully. Along the way, found and fixed a previously- unknown gap unrelated to the wire_codec migration itself: the .env config knob wasn't reaching any controller-launched service, across five independent launcher layers (compass_services.py's controller launch, process_supervisor's default args template, five custom args_template rows in seed SQL, and compass shell's own flag mapping plus a missing .env mirror variable) – all fixed, and documented in a new backlog capture (process-supervisor-config-passthrough-gap) proposing the target env-mapper-fallback end state for when this recurs. Documented the ORES_NATS_WIRE_FORMAT config knob in doc/recipes/compass/how_do_i_manage_the_environment_with_compass.org, bumped the .env-format version twice (18: added the knob itself; 19: added ores.shell's mirror variable). Confirmed the superseded raw-bytes-not-base64-for-images task is correctly ABANDONED with its pointer to this story intact. Restored the environment to json (the story's chosen default) and confirmed the regression path still starts cleanly. Full local ctest run shows one pre-existing, unrelated failure (ores.ore.core.tests, ORE XML trade-schema parsing errors predating this session's changes) – not touched by anything in this task.

Emacs 29.3 (Org mode 9.6.15)