Skip to content

[BUG] The aggregator refuses or 404s entity work its peers can serve #613

Description

@bburda

Bug report

Steps to reproduce

  1. Run an aggregator with a peer. Configure a scripts backend on the peer only.
  2. GET /api/v1/apps/<id>/scripts on the aggregator, then the same request on the owning peer.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions