Task: One per-config control-plane keyed by config_id, server-side resolution
Table of Contents
This page documents a task in the Feed lifecycle harmonization: asset-class-agnostic feed control-plane story. It captures the goal, current status, acceptance, and any notes or results.
Goal
One per-config start/stop/list control-plane for every asset class, keyed by config_id: the client sends the config_id, the server resolves the config, its children, and the refdata context, checks permissions, and starts the producer via the factory. The FX client-supplied-params pass-through (the no-auth anti-pattern) is deleted.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Feed lifecycle harmonization: asset-class-agnostic feed control-plane |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-08-11 |
Acceptance
- One start/stop/list handler serves both kinds, keyed by config_id, resolved server-side with permission checks — the IR curve pattern applied uniformly.
- market_feed_config_handler (client-supplied params, no auth) is deleted; its consumers use the config_id path.
- The NATS protocol carries one request/response shape per verb, kind-agnostic.
- Missing config, disabled config, and vintage-data-missing are reported the same way for both kinds.
Plan
One kind-agnostic per-config control-plane replaces the two per-kind
handlers: the client sends config_id, the server resolves the
config, its children, and the refdata context, checks permissions,
and starts the producer via the factory. The FX client-supplied-params
pass-through (no auth) is deleted. validate is deleted with it —
Qt's Validate Vintage moves to get_vintage_validity, which already
serves both kinds.
- One protocol: new
ores.synthetic.api/messaging/feed_config_protocol.hpp(ores::synthetic::messaging) carries the three per-config verbs, kind-agnostic —start_feed_request{config_id},stop_feed_request{config_id, source_name},list_feeds_request{} — on subjects =synthetic.v1.feed_configs.start|stop|list. The per-kind structs are deleted with their headers (ir_curve_feed_config_protocol.hppdeleted;market_feed_config_protocol.hppkeeps the folder cascade and vintage-validity shapes andfeed_kind_counts, dropping its per-config start/stop/list/validate structs). - One handler:
feed_config_handler.hppreplacesmarket_feed_config_handler.hppandir_curve_feed_config_handler.hpp(both deleted). It applies the IR pattern uniformly: auth viamake_request_context; resolution probes the FX repository first, then the IR repository, byconfig_id— themarket_data_generation_configcontainer has no kind discriminator, so the probe is the resolution. The resolved kind picks the permission check (synthetic::fx_spot_generation_configs:readvssynthetic::ir_curve_generation_configs:read), the enabled gates (config + container), the children reads, and the factory build input. Start dispatches throughmake_default_feed_factory().make(IR viawith_system_tenant+build_ir_curve_refdata_context); the IR builder'svintage_data_missing_error(FX does not throw it) is caught with the uniform vintage-data-missing message. Stop resolvesconfig_idtosource_nameand callsctrl_->stop(); list isctrl_->list()with an empty kind — every running feed, both kinds. - Uniform error shape: missing config ("Feed config not found: <id>"), disabled ("Feed config is not enabled: <id>"), already running, qualifier conflict (with the holding source name), and vintage-data-missing read the same for both kinds.
- Consumers migrate to the
config_idpath: the registrar subscribes 3 subjects (7 gone); QtMarketSimulatorWindowsends the unified requests for FX pairs and IR curves (its two list calls merge into one; its per-row FX Validate Vintage loop moves toget_vintage_validity, droppingresolved_price); ores.shellstart_feeddrops the client-side GMM parameter assembly and sendsconfig_id(stop_feedlikewise);tenant_handlerIR provisioning sends the unified start request (subject change). - Tests:
feed_config_handler_tests.cppmirrors the folder-cascade integration test (real NATS + scoped DB + per-run tenant + test-scoped subjects): seed a container with an FX config and an IR config plus children and refdata; verify start (both kinds), already-running, not-found, disabled, missing children, list, and stop. Registered intests/component_files.cmake.
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 |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1972 | [synthetic,marketdata,api] Per-config feed control plane keyed by config_id |
Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
Result
Shipped: one kind-agnostic per-config control plane.
- New
ores::synthetic::messagingprotocol (feed_config_protocol.hpp) with one request/response shape per verb onsynthetic.v1.feed_configs.start|stop|list:start_feed_request{config_id},stop_feed_request{config_id, source_name},list_feeds_request{}; the per-kind start/validate/ stop/list structs are deleted frommarket_feed_config_protocol.hpp(folder cascade and vintage-validity shapes stay) andir_curve_feed_config_protocol.hppis deleted. - One
feed_config_handlerserves both kinds: auth viamake_request_context; kind resolution probes the FX repository first, then the IR repository, byconfig_id(the container has no kind discriminator); the resolved kind picks its permission check, the config+container enabled gates, the children reads, and the factory build input (IR viawith_system_tenant+build_ir_curve_refdata_context). Missing, disabled, already-running, qualifier-conflict (naming the holding source_name), and vintage-data-missing read the same for both kinds. Stop resolvesconfig_idtosource_name(idempotent counts); list returns every running source_name, gated on both read permissions. market_feed_config_handler(client-supplied params, no auth) andir_curve_feed_config_handlerare deleted; the registrar subscribes the 3 unified subjects (7 gone). Consumers migrated: QtMarketSimulatorWindow(pair and IR loops sendconfig_id; the two list calls merge; FX Validate Vintage moved toget_vintage_validity, droppingresolved_price), ores.shellstart/stop feed(GMM assembly and the no-price-model check move server-side), andtenant_handlerprovisioning.- Integration tests
feed_config_handler_tests.cppcover start of both kinds byconfig_id, already-running, not-found and disabled (uniform messages), per-kind permission rejection (X-Error: forbidden), missing-children rejection, the qualifier-conflict report, list gated on both permissions, and stop byconfig_idorsource_name.
Acceptance met. Verification: full build green; the synthetic service component test green (296 assertions, 45 test cases, incl. the 6 new feed_config cases); rat green (exit 0) on a recreated database with all services up — the first rat attempt failed only on pre-existing iam.core repository tests writing deterministic faker names into the shared system tenant (rows left by an earlier rat run); a DB recreate (no schema change) restored the clean state and the rat passed.