Skip to content

feat(gateway): SOVD entity status and lifecycle control endpoints#437

Merged
bburda merged 14 commits into
mainfrom
feat/433-lifecycle-status-contract
Jun 20, 2026
Merged

feat(gateway): SOVD entity status and lifecycle control endpoints#437
bburda merged 14 commits into
mainfrom
feat/433-lifecycle-status-contract

Conversation

@bburda

@bburda bburda commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the SOVD ISO 17978-3 entity status and lifecycle endpoints to the gateway core:

  • GET /{apps|components}/{id}/status - returns ready / notReady plus the URIs of the
    transitions the entity supports.
  • PUT /{apps|components}/{id}/status/{start,restart,force-restart,shutdown,force-shutdown}.

This is the core contract only. Status read works from runtime state: an App is ready when its
node is online, and the host Component is ready while it is reachable. The five control
transitions are routed to a new per-entity LifecycleProvider interface that has no implementation
in this PR, so control returns 501 until a substrate-owning plugin registers a provider. The
actuation work (ROS lifecycle nodes, process / container / systemd, host reboot) is out of scope
here and is tracked in follow-up issues #434, #435, #436.

Also in this PR:

  • New LifecycleProvider interface plus a LifecycleProviderErrorInfo error struct, routed through
    PluginManager like the existing operation provider.
  • LifecycleStatusResponse DTO, and a status link advertised on the app and component detail
    responses.
  • RBAC: GET status is allowed for the viewer role and above; the PUT transitions for operator and
    above.
  • Design doc, README, and REQ_INTEROP_076 marked verified (077-081 stay open since control is a
    501 stub).

Making the required role per endpoint configurable is tracked separately in #432. The version bump
is left to a maintainer at release time. No breaking changes; everything here is additive.

Issue

Type

  • New feature or tests

Testing

Unit (GTest):

  • DTO serialization, including the hyphenated wire keys (force-restart, force-shutdown).
  • Provider routing in PluginManager (owned vs unowned entity).
  • The status / lifecycle handlers: status read (ready / notReady for apps and the host
    component), 404 before 501 for an unknown entity, the five PUT transitions returning 501 with no
    provider, and the provider-present path via a mock provider (filled transition URIs, 202 +
    Location, and precondition-not-fulfilled -> 409).
  • The new RBAC permissions, including a viewer-denied-PUT case.

Integration (launch_testing): GET /apps/{id}/status and GET /components/{host}/status return
ready, and PUT /apps/{id}/status/restart returns 501. Tagged @verifies REQ_INTEROP_076.

clang-tidy is clean on the changed files.

Checklist

  • Breaking changes are clearly described (none - additive)
  • Tests were added or updated if needed
  • Docs were updated if behavior or public API changed

bburda added 12 commits June 18, 2026 15:19
…lers

- Change handle_transition parameter from std::string to std::string_view
  (performance-unnecessary-value-param: parameter was only read, not modified)
- Replace std::move on trivially-copyable http::NoContent with direct
  construction via std::make_pair(http::NoContent{}, std::move(att))
  (performance-move-const-arg: move on trivial struct is a no-op)
Replace non-fatal EXPECT_TRUE + unconditional value() access with an
ADD_FAILURE guard that returns an empty json on error, eliminating UB
(empty tl::expected dereference) on a failing path.
Add GET:/api/v1/{apps,components}/*/status to VIEWER, OPERATOR, CONFIGURATOR lists.
Add PUT:/api/v1/{apps,components}/*/status/* to OPERATOR and CONFIGURATOR lists.
ADMIN is already covered by PUT:/api/v1/**.
Add test_lifecycle.test.py: launch_testing integration test exercising
GET /apps/{id}/status and GET /components/{id}/status (expects "ready"
for online entities) and PUT /apps/{id}/status/restart (expects 501
when no lifecycle provider registered). Host component id resolved
dynamically via GET /components items[0]. REQ_INTEROP_076 marked
verified.
Add design/lifecycle.rst covering the 6 status/lifecycle routes
(GET status + 5 PUT transitions for apps and components), status
read semantics per entity type, the LifecycleProvider seam, and
error mapping (403/409/501). Note that REQ_INTEROP_076 is verified
and 077-081 remain open until a substrate plugin lands.

Add lifecycle to the gateway design toctree. Update README with
the new endpoint list and a SOVD compliance table.
…es and cover provider-present path

- Add .response(202, ...) to all 10 PUT transition routes (apps + components)
  so the published OpenAPI reflects the actual 202 Accepted response instead
  of the default 200 from the DTO template.
- Add .operation_id(...) to all 12 lifecycle routes (2 GET + 10 PUT) following
  the camelCase verb+Entity+Resource pattern used throughout rest_server.cpp.
- Fix misleading comment claiming PUT/GET ordering prevents shadowing; GET and
  PUT are different methods and cannot shadow each other.
- Add LifecycleHandlersWithProviderTest fixture with 3 new test cases covering
  the provider-present handler path: URI filling on GET status, 202+Location on
  accepted transition, and 409 mapping for PreconditionFailed provider errors.
The lifecycle status routes are tagged "Lifecycle" but the tag was not
registered in the global OpenAPI tags array, so the tag-consistency check
in the health integration test failed.
@bburda bburda self-assigned this Jun 18, 2026
@bburda bburda requested a review from mfaferek93 June 18, 2026 18:30
@bburda bburda marked this pull request as ready for review June 19, 2026 16:40
Copilot AI review requested due to automatic review settings June 19, 2026 16:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds SOVD ISO 17978-3 entity lifecycle status and control contract to ros2_medkit_gateway, exposing /status endpoints for Apps and Components, with control transitions delegated to a new plugin interface (returning 501 until implemented by a substrate plugin).

Changes:

  • Introduces LifecycleStatusResponse DTO + LifecycleProvider interface, and routes GET /{apps|components}/{id}/status + PUT /.../status/{start|restart|force-restart|shutdown|force-shutdown}.
  • Wires lifecycle provider routing through PluginManager, and advertises a status link on app/component detail responses.
  • Updates RBAC defaults for status read/control, adds unit + integration coverage, and adds design/requirements/README documentation.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/ros2_medkit_integration_tests/test/features/test_lifecycle.test.py Launch-testing coverage for GET status (ready) and PUT transition (501).
src/ros2_medkit_gateway/test/test_lifecycle_provider_routing.cpp Unit tests for PluginManager lifecycle-provider resolution by entity ownership.
src/ros2_medkit_gateway/test/test_lifecycle_handlers.cpp Unit tests for lifecycle handlers (default path, provider path, error mapping, link advertisement).
src/ros2_medkit_gateway/test/test_lifecycle_dto.cpp DTO JSON writer tests incl. hyphenated transition keys.
src/ros2_medkit_gateway/test/test_auth_config.cpp RBAC unit tests for status/lifecycle permissions.
src/ros2_medkit_gateway/src/plugins/plugin_manager.cpp Adds lifecycle provider caching and lookup by owned entity.
src/ros2_medkit_gateway/src/openapi/capability_generator.cpp Adds “Lifecycle” capability to the root capability list.
src/ros2_medkit_gateway/src/http/rest_server.cpp Registers lifecycle routes and instantiates LifecycleHandlers.
src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp Implements GET status and PUT transition logic (default cache-derived status + provider delegation).
src/ros2_medkit_gateway/src/http/handlers/discovery_handlers.cpp Advertises status URI on component/app detail responses.
src/ros2_medkit_gateway/src/core/auth/auth_config.cpp Adds default role permissions for GET status (viewer+) and PUT transitions (operator+).
src/ros2_medkit_gateway/README.md Documents new endpoints and updates SOVD compliance table entries.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/registry.hpp Registers lifecycle DTO in the global DTO registry.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/lifecycle.hpp Adds LifecycleStatusResponse DTO contract.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/enums.hpp Adds allowed lifecycle status values.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/entities.hpp Adds status link field to app/component detail DTOs.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/providers/lifecycle_provider.hpp Defines LifecycleProvider interface + provider error struct/enum.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/plugins/plugin_manager.hpp Extends loaded-plugin provider pointers with lifecycle provider.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/rest_server.hpp Adds lifecycle_handlers_ member to RESTServer.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/lifecycle_handlers.hpp Declares lifecycle handler class.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/handlers.hpp Exposes lifecycle handlers via the convenience include.
src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/error_codes.hpp Adds SOVD-standard error codes used by lifecycle provider mapping.
src/ros2_medkit_gateway/design/lifecycle.rst Design doc describing routing, semantics, provider seam, and requirement coverage.
src/ros2_medkit_gateway/design/index.rst Adds lifecycle doc to the design-doc index.
src/ros2_medkit_gateway/CMakeLists.txt Builds new handler + adds new test targets.
docs/requirements/specs/lifecycle.rst Marks REQ_INTEROP_076 as verified.

Comment thread src/ros2_medkit_integration_tests/test/features/test_lifecycle.test.py Outdated
Comment thread src/ros2_medkit_gateway/design/lifecycle.rst Outdated
Comment thread src/ros2_medkit_gateway/design/lifecycle.rst Outdated
Comment thread src/ros2_medkit_gateway/src/core/auth/auth_config.cpp Outdated
Comment thread src/ros2_medkit_gateway/src/core/auth/auth_config.cpp Outdated
Comment thread src/ros2_medkit_gateway/src/core/auth/auth_config.cpp Outdated
Comment thread src/ros2_medkit_gateway/src/http/rest_server.cpp
Comment thread src/ros2_medkit_gateway/src/plugins/plugin_manager.cpp Outdated
Comment thread src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp Outdated
Comment thread src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp
Comment thread src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp
Comment thread src/ros2_medkit_gateway/src/core/auth/auth_config.cpp Outdated
bburda added 2 commits June 19, 2026 19:49
- Log plugin exceptions server-side and return a static message from the
  lifecycle handler instead of forwarding the exception text to the client,
  matching the operation/data/fault provider-delegation convention.
- Advertise the status sub-resource in the entity capabilities array
  (add Capability::STATUS) so the top-level URIs and the capabilities array
  describe the same set of collections.
- Add handler unit tests for the provider-error mappings (403, provider-driven
  501, clamped 5xx) and the GET provider-error path.
- Assert error_code values (not-implemented, entity-not-found) in the
  lifecycle integration test instead of only checking the field is present.
- Fix the design doc: route registration order is irrelevant because GET and
  PUT are different HTTP methods; use "verified" not "closed" for the status.
- Resolve LifecycleProvider via an extern "C" get_lifecycle_provider query in
  the plugin ABI instead of dynamic_cast across the dlopen boundary, where RTTI
  is unreliable - a real provider could cast to null and silently never
  register, leaving lifecycle control at 501 forever.
- Derive Component readiness from a real liveness signal: the host Component is
  ready while reachable; any other Component is ready when at least one hosted
  App is online. host_metadata is only a host marker, not a liveness signal.
- Map a provider's EntityNotFound to 404 entity-not-found instead of a 500
  plugin-error.
- Validate the provider-supplied status against {ready, notReady} before
  writing it, guarding the SOVD contract against a misbehaving plugin.
- Gate the destructive shutdown / force-shutdown transitions behind the
  configurator role; operator keeps start / restart / force-restart.
- Add the <optional> include to the lifecycle DTO, scope the RBAC status
  comments to apps and components, and assert exactly one host Component in the
  integration test.
- Document the status and transition endpoints in docs/api/rest.rst.
- Tests for the provider ABI path, component liveness, the 404 mapping, the
  invalid-status guard, and the per-transition RBAC.
@bburda bburda merged commit bf548e9 into main Jun 20, 2026
15 of 16 checks passed
@bburda bburda deleted the feat/433-lifecycle-status-contract branch June 20, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Entity status endpoints and lifecycle control contract (gateway core)

3 participants