Bug report
Steps to reproduce
- Run an aggregator with a peer. Configure a scripts backend on the peer only.
GET /api/v1/apps/<id>/scripts on the aggregator, then the same request on the owning peer.
- For a merged entity, meaning an Area or Function with contributors on more than one gateway:
GET /api/v1/areas/<id>/operations on the aggregator, then
POST /api/v1/areas/<id>/operations/<op-owned-by-a-peer>/executions.
Expected behavior
The aggregator serves what its peers can serve, or it does not advertise it. An operation that
appears in a list can be executed through the gateway that listed it.
Actual behavior
Scripts. Same path, two gateways, seconds apart:
aggregator -> 501 {"error_code": "not-implemented", "message": "Scripts backend not configured"}
owning peer -> 200 {"_links": {...}, "items": [{...}]}
The message describes the aggregator's own backend, but the entity it names belongs to a peer
that has one. So the answer is wrong about whose configuration is missing.
Operations on a merged entity. GET .../operations returns 200 and includes operations
contributed by peers. POST .../operations/<that-op>/executions returns
404 operation-not-found. The aggregator lists operations it will not execute.
Environment
- ros2_medkit version: main (a268379), gateway vendor_info.version 0.6.0, SOVD API 1.0.0
- ROS 2 distro: Jazzy
- OS: Ubuntu 24.04
Additional information
Scripts: the backend check runs before the peer-forward check.
// src/ros2_medkit_gateway/src/http/handlers/script_handlers.cpp:128-130
if (!script_mgr_ || !script_mgr_->has_backend()) {
return tl::unexpected(make_error(501, ERR_NOT_IMPLEMENTED, "Scripts backend not configured"));
}
...
auto entity_result = ctx_.validate_entity_for_route(req, entity_id); // :138, never reached
The same order appears in all eight script handlers. The checks are at lines 128, 179, 264, 312,
358, 442, 496 and 549, and the matching validate_entity_for_route calls are at 138, 189, 283,
331, 377, 469, 523 and 576.
ScriptManager is always constructed (src/gateway_node.cpp:947), so the routes are always
registered, but backend_ stays null unless this gateway has its own scripts directory or a
plugin ScriptProvider.
This makes /scripts the only entity resource collection whose peer path cannot be reached at
all. Every other collection at least reaches validate_entity_for_route and is proxied for a
routed remote leaf (src/http/handlers/handler_context.cpp:315-320).
Operations: the list fans out, the execute does not. list_operations merges peer items
unconditionally (src/http/handlers/operation_handlers.cpp:407). create_execution calls
validate_entity_for_route (:545), which only proxies for a routed remote leaf, then resolves
the operation against the local cache and returns 404 operation-not-found (:628-631).
Merged Areas, merged Functions and hierarchical parent Components are deliberately kept out of
the routing table (design/aggregation.rst:276-282), so is_remote is false for them and
neither mechanism applies.
The execution lifecycle cannot be routed even for genuinely remote entities. list_executions
(:727), get_execution (:796), cancel_execution (:852) and update_execution (:915)
call validate_entity_id instead of validate_entity_for_route, so they never proxy. An
execution id created on a peer returns 404 on the aggregator.
The same one-way pattern applies to {entity}/data, {entity}/configurations and
{entity}/logs. The list is merged from peers, but the single-item read and write
(data_handlers.cpp:349,465, config_handlers.cpp:451,565, log_handlers.cpp:219,252) only
reach a peer through validate_entity_for_route, never for a merged entity.
Capabilities are not filtered by peer aggregation. EntityCapabilities::for_type
(src/core/models/entity_capabilities.cpp:19-109) is a static per-type table. It does have an
aggregated_collections_ set with an is_aggregated() accessor
(entity_capabilities.cpp:127-128), but that describes aggregation from sub-entities inside one
gateway, for example a Function collecting data from its hosted Apps, as its own doc comment says
(include/ros2_medkit_gateway/core/models/entity_capabilities.hpp:88-94). It is not connected to
AggregationManager or to peer fan-out, and is_aggregated() has no caller outside the class.
Proxied peer detail responses are copied as they are
(src/core/aggregation/peer_client.cpp:660-670), with only five headers allowed through and the
body never rewritten. Capability hrefs are host-relative
(src/core/http/handlers/capability_builder.cpp:80-83). So a client reads the peer's scripts
href out of a proxied response, follows it back to the aggregator, and gets the 501. This
overlaps #515, which covers the per-entity side of the same problem.
Bug report
Steps to reproduce
GET /api/v1/apps/<id>/scriptson the aggregator, then the same request on the owning peer.GET /api/v1/areas/<id>/operationson the aggregator, thenPOST /api/v1/areas/<id>/operations/<op-owned-by-a-peer>/executions.Expected behavior
The aggregator serves what its peers can serve, or it does not advertise it. An operation that
appears in a list can be executed through the gateway that listed it.
Actual behavior
Scripts. Same path, two gateways, seconds apart:
The message describes the aggregator's own backend, but the entity it names belongs to a peer
that has one. So the answer is wrong about whose configuration is missing.
Operations on a merged entity.
GET .../operationsreturns 200 and includes operationscontributed by peers.
POST .../operations/<that-op>/executionsreturns404 operation-not-found. The aggregator lists operations it will not execute.Environment
Additional information
Scripts: the backend check runs before the peer-forward check.
The same order appears in all eight script handlers. The checks are at lines 128, 179, 264, 312,
358, 442, 496 and 549, and the matching
validate_entity_for_routecalls are at 138, 189, 283,331, 377, 469, 523 and 576.
ScriptManageris always constructed (src/gateway_node.cpp:947), so the routes are alwaysregistered, but
backend_stays null unless this gateway has its own scripts directory or aplugin ScriptProvider.
This makes
/scriptsthe only entity resource collection whose peer path cannot be reached atall. Every other collection at least reaches
validate_entity_for_routeand is proxied for arouted remote leaf (
src/http/handlers/handler_context.cpp:315-320).Operations: the list fans out, the execute does not.
list_operationsmerges peer itemsunconditionally (
src/http/handlers/operation_handlers.cpp:407).create_executioncallsvalidate_entity_for_route(:545), which only proxies for a routed remote leaf, then resolvesthe operation against the local cache and returns
404 operation-not-found(:628-631).Merged Areas, merged Functions and hierarchical parent Components are deliberately kept out of
the routing table (
design/aggregation.rst:276-282), sois_remoteis false for them andneither mechanism applies.
The execution lifecycle cannot be routed even for genuinely remote entities.
list_executions(
:727),get_execution(:796),cancel_execution(:852) andupdate_execution(:915)call
validate_entity_idinstead ofvalidate_entity_for_route, so they never proxy. Anexecution id created on a peer returns 404 on the aggregator.
The same one-way pattern applies to
{entity}/data,{entity}/configurationsand{entity}/logs. The list is merged from peers, but the single-item read and write(
data_handlers.cpp:349,465,config_handlers.cpp:451,565,log_handlers.cpp:219,252) onlyreach a peer through
validate_entity_for_route, never for a merged entity.Capabilities are not filtered by peer aggregation.
EntityCapabilities::for_type(
src/core/models/entity_capabilities.cpp:19-109) is a static per-type table. It does have anaggregated_collections_set with anis_aggregated()accessor(
entity_capabilities.cpp:127-128), but that describes aggregation from sub-entities inside onegateway, for example a Function collecting data from its hosted Apps, as its own doc comment says
(
include/ros2_medkit_gateway/core/models/entity_capabilities.hpp:88-94). It is not connected toAggregationManageror to peer fan-out, andis_aggregated()has no caller outside the class.Proxied peer detail responses are copied as they are
(
src/core/aggregation/peer_client.cpp:660-670), with only five headers allowed through and thebody never rewritten. Capability hrefs are host-relative
(
src/core/http/handlers/capability_builder.cpp:80-83). So a client reads the peer'sscriptshref out of a proxied response, follows it back to the aggregator, and gets the 501. This
overlaps #515, which covers the per-entity side of the same problem.