Summary
Today the gateway package compiles all of its source files into a single gateway_lib static library that links rclcpp, rcl_interfaces, action messages, ros2_medkit_msgs, and ros2_medkit_serialization. Most of that source — HTTP routing, request handlers, JWT authentication, fault model (debounce, SQLite storage, correlation), peer aggregation (mDNS discovery, entity merger, SSE proxy), the manifest parser, the entity cache, the lock/bulk-data/subscription/script/update managers, the OpenAPI builder, and the provider interface contracts — has no inherent dependency on ROS. Mixing the two in one library produces several friction points:
- Testing requires a full ROS environment. Every gateway unit test brings in
ament_target_dependencies and pulls rclcpp onto the link line, even when the code under test (e.g. auth_manager, fault_storage, entity_merger, peer_client) doesn't touch ROS APIs.
- Slower iteration on business logic. The precompiled-header set bundles
rclcpp/rclcpp.hpp, so any change in a neutral header invalidates a PCH that includes ROS headers and triggers larger rebuilds than necessary.
- No structural enforcement that new neutral code stays neutral — there is no CI guard preventing somebody from quietly adding
#include <rclcpp/rclcpp.hpp> inside the JWT manager or the SQLite trigger store.
- Provider abstractions are blurred. The
DataProvider / OperationProvider / FaultProvider / LogProvider / IntrospectionProvider interfaces are pure C++17, but they sit alongside their ROS-specific default implementations (e.g. Ros2TopicDataProvider) under the same include root, making the interface/implementation boundary harder to reason about.
Proposed solution (optional)
Split the package's sources into two named layers, both inside the existing ros2_medkit_gateway package, without splitting into a separate colcon package yet:
core/ — middleware-neutral business logic. Compiles into a new gateway_core static library that links only header-only and C-level externals (cpp-httplib, nlohmann/json, yaml-cpp, tl::expected, jwt-cpp, OpenSSL, SQLite, dl). No rclcpp, no rcl_interfaces, no message-package transitive includes. Enforced by:
- a grep-based linter (
scripts/check_core_purity.sh wired as gateway_core_purity CTest);
- a link-time smoke test (
test_gateway_core_smoke) that compiles a translation unit including a sampling of core/ headers and links exclusively against gateway_core + GTest, with no ament_target_dependencies. A regression in either guard fails the build.
- The remainder of the source tree —
gateway_node, the ROS-coupled managers, runtime_discovery, ros2_topic_data_provider, the trigger subscribers, etc. — stays at its current path and forms a gateway_ros2 static library that publicly links gateway_core.
The gateway_node executable and existing tests link gateway_ros2 so they transitively get both layers; nothing changes for downstream consumers of the gateway. Headers that move acquire a one-line forwarding shim at their old paths so out-of-tree plugins continue to compile without modification.
This issue tracks only the structural relocation. Subsequent issues will cover the per-manager refactors (data access, operation, configuration, fault facade, log, trigger) that introduce provider injection so the managers themselves move into core/.
Additional context (optional)
Outcome metrics expected after the relocation:
- 35 source files compile via
gateway_core without any ROS link dependency.
- Two structural CI guards:
gateway_core_purity (grep) and gateway_core_smoke (link-time).
- PCH split:
gateway_core PCH contains std + json + httplib + tl::expected; gateway_ros2 PCH adds rclcpp/rclcpp.hpp on top.
- Backwards-compat shim headers at every legacy path so out-of-tree consumers (graph_provider, opcua, sovd_service_interface, linux_introspection, param_beacon, topic_beacon) compile unchanged.
- Existing test suite (~2500 unit, ~3200 integration, ~2600 clang-tidy targets) stays green.
Summary
Today the gateway package compiles all of its source files into a single
gateway_libstatic library that linksrclcpp,rcl_interfaces, action messages,ros2_medkit_msgs, andros2_medkit_serialization. Most of that source — HTTP routing, request handlers, JWT authentication, fault model (debounce, SQLite storage, correlation), peer aggregation (mDNS discovery, entity merger, SSE proxy), the manifest parser, the entity cache, the lock/bulk-data/subscription/script/update managers, the OpenAPI builder, and the provider interface contracts — has no inherent dependency on ROS. Mixing the two in one library produces several friction points:ament_target_dependenciesand pullsrclcpponto the link line, even when the code under test (e.g.auth_manager,fault_storage,entity_merger,peer_client) doesn't touch ROS APIs.rclcpp/rclcpp.hpp, so any change in a neutral header invalidates a PCH that includes ROS headers and triggers larger rebuilds than necessary.#include <rclcpp/rclcpp.hpp>inside the JWT manager or the SQLite trigger store.DataProvider/OperationProvider/FaultProvider/LogProvider/IntrospectionProviderinterfaces are pure C++17, but they sit alongside their ROS-specific default implementations (e.g.Ros2TopicDataProvider) under the same include root, making the interface/implementation boundary harder to reason about.Proposed solution (optional)
Split the package's sources into two named layers, both inside the existing
ros2_medkit_gatewaypackage, without splitting into a separate colcon package yet:core/— middleware-neutral business logic. Compiles into a newgateway_corestatic library that links only header-only and C-level externals (cpp-httplib, nlohmann/json, yaml-cpp, tl::expected, jwt-cpp, OpenSSL, SQLite, dl). Norclcpp, norcl_interfaces, no message-package transitive includes. Enforced by:scripts/check_core_purity.shwired asgateway_core_purityCTest);test_gateway_core_smoke) that compiles a translation unit including a sampling ofcore/headers and links exclusively againstgateway_core+ GTest, with noament_target_dependencies. A regression in either guard fails the build.gateway_node, the ROS-coupled managers,runtime_discovery,ros2_topic_data_provider, the trigger subscribers, etc. — stays at its current path and forms agateway_ros2static library that publicly linksgateway_core.The
gateway_nodeexecutable and existing tests linkgateway_ros2so they transitively get both layers; nothing changes for downstream consumers of the gateway. Headers that move acquire a one-line forwarding shim at their old paths so out-of-tree plugins continue to compile without modification.This issue tracks only the structural relocation. Subsequent issues will cover the per-manager refactors (data access, operation, configuration, fault facade, log, trigger) that introduce provider injection so the managers themselves move into
core/.Additional context (optional)
Outcome metrics expected after the relocation:
gateway_corewithout any ROS link dependency.gateway_core_purity(grep) andgateway_core_smoke(link-time).gateway_corePCH contains std + json + httplib + tl::expected;gateway_ros2PCH adds rclcpp/rclcpp.hpp on top.