Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8eb991e
feat(scripts): add ScriptProvider interface and types
bburda Mar 17, 2026
6a3f8af
feat(scripts): add ScriptManager with unit tests
bburda Mar 17, 2026
b5d454e
feat(scripts): add script error code constants
bburda Mar 17, 2026
a2c2371
feat(scripts): add ScriptHandlers with SOVD error mapping
bburda Mar 17, 2026
edbd106
feat(scripts): register routes via RouteRegistry and add scripts capa…
bburda Mar 17, 2026
63c6add
feat(scripts): add DefaultScriptProvider with manifest + filesystem CRUD
bburda Mar 17, 2026
1bced09
feat(scripts): implement subprocess execution with arg passing and ti…
bburda Mar 17, 2026
2a4386f
feat(scripts): add ScriptProvider plugin support, bump API to v4
bburda Mar 17, 2026
7c9f337
feat(scripts): wire ScriptManager into GatewayNode
bburda Mar 17, 2026
a671e87
test(scripts): add integration tests for SOVD script endpoints
bburda Mar 17, 2026
308a7c3
fix(scripts): use single quotes in demo Python script (flake8 Q000)
bburda Mar 17, 2026
6ce8ecd
fix(scripts): validate script_id and execution_id to prevent path tra…
bburda Mar 17, 2026
0db141d
fix(scripts): fix deadlock in delete_execution, concurrency race, and…
bburda Mar 17, 2026
770f41a
fix(scripts): correct @verifies tags to REQ_INTEROP_040-047 and remov…
bburda Mar 17, 2026
bb04043
fix(scripts): remove unimplemented manifest_path parameter
bburda Mar 17, 2026
6d46891
fix(scripts): POSIX hardening - O_CLOEXEC, output caps, PID safety
bburda Mar 17, 2026
86bccb0
fix(scripts): HTTP response ordering, conditional capabilities, disti…
bburda Mar 17, 2026
315bb2e
fix(scripts): add scripts config to gateway_params.yaml and RBAC rules
bburda Mar 17, 2026
58ac529
docs(scripts): update README, REST API docs, and design docs
bburda Mar 17, 2026
2774e9e
fix(scripts): fix vendor error code assertion in CI and make pid atomic
bburda Mar 17, 2026
97c01b2
test(scripts): add comprehensive integration tests - all formats, par…
bburda Mar 17, 2026
b7cb324
fix(scripts): validate execution ownership and block delete of runnin…
bburda Mar 18, 2026
ed4cda9
fix(scripts): add collection access validation, reuse error constants…
bburda Mar 18, 2026
6eb291b
test(scripts): add handler unit tests for request validation and erro…
bburda Mar 18, 2026
8c86c9d
fix(scripts): clarify execution_type support in docs and clean up tes…
bburda Mar 18, 2026
d669826
fix(scripts): add POSIX headers, handle EINTR, graceful shutdown, pol…
bburda Mar 18, 2026
b6d22dc
fix(scripts): ScriptManager null guards, delete copy/move, fix api_pa…
bburda Mar 18, 2026
9397044
test(scripts): adversarial input validation, control validation, File…
bburda Mar 19, 2026
edbee7c
feat(scripts): add allow_uploads config toggle for hardened deployments
bburda Mar 19, 2026
4981703
fix(scripts): add execution history eviction and stdin write size limit
bburda Mar 19, 2026
6065906
fix(scripts): ScriptManager as safety/observability layer - try/catch…
bburda Mar 19, 2026
12ee303
fix: resolve ROS_DOMAIN_ID clashes between gateway and fault_manager …
bburda Mar 19, 2026
0b4e855
fix(scripts): Content-Type check, extract entity_type helper, add _li…
bburda Mar 19, 2026
87563ff
fix(scripts): manifest validation, UUID IDs, TOCTOU fix, timeout safe…
bburda Mar 19, 2026
1300147
fix(auth): add missing sub-resource RBAC patterns for all collections
bburda Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Feedback welcome on [#265](https://github.com/selfpatch/ros2_medkit/issues/265).
| 📋 Logs | **Available** | Log sources, entries, and configuration |
| 🔁 Entity Lifecycle | Planned | Start, restart, shutdown control |
| 🔐 Modes & Locking | Planned | Target mode control and resource locking |
| 📝 Scripts | Planned | Diagnostic script upload and execution |
| 📝 Scripts | **Available** | Diagnostic script upload and execution (SOVD 7.15) |
| 🧹 Clear Data | Planned | Clear cached and learned diagnostic data |
| 📞 Communication Logs | Planned | Protocol-level communication logging |

Expand Down
126 changes: 126 additions & 0 deletions docs/api/rest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,125 @@ Subscribe to a specific configuration parameter:
"duration": 120
}

Scripts
-------

Upload, manage, and execute diagnostic scripts on entities.
*(ISO 17978-3, 7.15)*

Scripts are available on **Components** and **Apps** entity types.
The feature must be enabled by setting ``scripts.scripts_dir`` in the gateway configuration.

Upload Script
~~~~~~~~~~~~~

``POST /api/v1/{entity_type}/{entity_id}/scripts``
Upload a diagnostic script via ``multipart/form-data``.

- **file** (required): The script file (Python, bash, or sh)
- **metadata** (optional): JSON with name, description, parameters_schema

Response: **201 Created** with ``Location`` header pointing to the new script.

.. note::

Uploads can be disabled by setting ``scripts.allow_uploads: false`` in the
gateway configuration. When disabled, POST returns 400. Pre-deployed
manifest scripts remain available for execution.

List Scripts
~~~~~~~~~~~~

``GET /api/v1/{entity_type}/{entity_id}/scripts``
List all scripts for an entity. Returns ``{"items": [...]}``.

Get Script
~~~~~~~~~~

``GET /api/v1/{entity_type}/{entity_id}/scripts/{script_id}``
Get metadata for a specific script.

Delete Script
~~~~~~~~~~~~~

``DELETE /api/v1/{entity_type}/{entity_id}/scripts/{script_id}``
Delete an uploaded script. Returns **204 No Content**.
Returns **409** if the script is manifest-managed or currently executing.

Start Execution
~~~~~~~~~~~~~~~

``POST /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions``
Start a new execution of a script.

**Request Body:**

.. code-block:: json

{
"execution_type": "now",
"parameters": {"threshold": 0.1}
}

.. list-table::
:header-rows: 1
:widths: 25 15 10 50

* - Attribute
- Type
- Conv
- Description
* - ``execution_type``
- string
- M
- When to run: ``now``, ``on_restart``, ``now_and_on_restart``, ``once_on_restart``
* - ``parameters``
- object
- O
- Input parameters for the script
* - ``proximity_response``
- string
- O
- Co-location proof token

.. note::

The built-in script backend supports only ``now``. Other execution types
(``on_restart``, ``now_and_on_restart``, ``once_on_restart``) require a
plugin-provided ScriptProvider and will return 400 ``invalid-parameter``
if not supported.

Response: **202 Accepted** with ``Location`` header pointing to the execution status.

Get Execution Status
~~~~~~~~~~~~~~~~~~~~

``GET /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}``
Poll the status of a script execution.

Status values: ``prepared``, ``running``, ``completed``, ``failed``, ``terminated``

Terminate Execution
~~~~~~~~~~~~~~~~~~~

``PUT /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}``
Send a termination action to a running execution.

**Request Body:**

.. code-block:: json

{"action": "stop"}

Action values: ``stop`` (SIGTERM), ``forced_termination`` (SIGKILL).

Delete Execution
~~~~~~~~~~~~~~~~

``DELETE /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}``
Remove a completed/terminated execution resource. Returns **204 No Content**.
Returns **409** if the execution is still running.

Rate Limiting
-------------

Expand Down Expand Up @@ -1621,6 +1740,7 @@ use cases benefit.
- Bulk Data (``/bulk-data``) with custom categories and rosbag downloads
- Software Updates (``/updates``) with async prepare/execute lifecycle
- Cyclic Subscriptions (``/cyclic-subscriptions``) with SSE-based delivery
- Scripts (``/scripts``) with upload, execution, and lifecycle management

**Pragmatic Extensions:**

Expand Down Expand Up @@ -1679,6 +1799,12 @@ extends this to areas and functions where aggregation makes practical sense:
- yes
- yes
- apps, components
* - scripts
- \-
- yes
- yes
- \-
- apps, components

Other extensions beyond SOVD:

Expand Down
8 changes: 0 additions & 8 deletions docs/requirements/specs/scripts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,6 @@ Scripts

The endpoint shall start an execution of the addressed script on the entity.

.. req:: GET /{entity}/scripts/{id}/executions
:id: REQ_INTEROP_045
:status: open
:tags: Scripts

The endpoint shall list executions of the addressed script on the entity.

.. req:: GET /{entity}/scripts/{id}/executions/{exec-id}
:id: REQ_INTEROP_046
:status: open
Expand All @@ -56,4 +49,3 @@ Scripts
:tags: Scripts

The endpoint shall control or modify the addressed script execution, if supported.

25 changes: 23 additions & 2 deletions src/ros2_medkit_gateway/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ add_library(gateway_lib STATIC
src/auth/auth_requirement_policy.cpp
# Updates module
src/updates/update_manager.cpp
# Scripts module
src/script_manager.cpp
src/default_script_provider.cpp
# HTTP handlers - scripts
src/http/handlers/script_handlers.cpp
# HTTP handlers - updates
src/http/handlers/update_handlers.cpp
# Plugin framework
Expand Down Expand Up @@ -453,6 +458,19 @@ if(BUILD_TESTING)
ament_add_gtest(test_update_manager test/test_update_manager.cpp)
target_link_libraries(test_update_manager gateway_lib)

# Add script manager tests
ament_add_gtest(test_script_manager test/test_script_manager.cpp)
target_link_libraries(test_script_manager gateway_lib)

# Add default script provider tests
ament_add_gtest(test_default_script_provider test/test_default_script_provider.cpp)
target_link_libraries(test_default_script_provider gateway_lib)

# Add script handler tests
ament_add_gtest(test_script_handlers test/test_script_handlers.cpp)
target_link_libraries(test_script_handlers gateway_lib)
set_tests_properties(test_script_handlers PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=69")

# Add data handler tests
ament_add_gtest(test_data_handlers test/test_data_handlers.cpp)
target_link_libraries(test_data_handlers gateway_lib)
Expand Down Expand Up @@ -554,7 +572,7 @@ if(BUILD_TESTING)
# Capability generator tests (OpenAPI spec generation engine)
ament_add_gtest(test_capability_generator test/test_capability_generator.cpp)
target_link_libraries(test_capability_generator gateway_lib)
set_tests_properties(test_capability_generator PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=67")
set_tests_properties(test_capability_generator PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=74")

# Route registry tests (OpenAPI route registration and path conversion)
ament_add_gtest(test_route_registry test/test_route_registry.cpp)
Expand All @@ -572,7 +590,7 @@ if(BUILD_TESTING)
# Lock handlers tests
ament_add_gtest(test_lock_handlers test/test_lock_handlers.cpp)
target_link_libraries(test_lock_handlers gateway_lib)
set_tests_properties(test_lock_handlers PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=70")
set_tests_properties(test_lock_handlers PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=73")

# Apply coverage flags to test targets
if(ENABLE_COVERAGE)
Expand Down Expand Up @@ -605,6 +623,9 @@ if(BUILD_TESTING)
test_cyclic_subscription_handlers
test_sse_transport_provider
test_update_manager
test_script_manager
test_default_script_provider
test_script_handlers
test_data_handlers
test_auth_handlers
test_health_handlers
Expand Down
21 changes: 21 additions & 0 deletions src/ros2_medkit_gateway/config/gateway_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,27 @@ ros2_medkit_gateway:
# Default: false
enabled: false

# ---------------------------------------------------------------------------
# Scripts Configuration
# ---------------------------------------------------------------------------
# Diagnostic scripts: upload, manage, and execute scripts on entities.
# Set scripts_dir to enable. Leave empty to disable (all script endpoints return 501).
scripts:
# Directory for storing uploaded scripts. Empty = feature disabled.
scripts_dir: ""
# Allow uploading scripts via HTTP. Set to false for hardened deployments
# that only use manifest-defined scripts.
allow_uploads: true
# Maximum uploaded script file size in megabytes
max_file_size_mb: 10
# Maximum number of scripts executing concurrently
max_concurrent_executions: 5
# Default timeout per execution in seconds
default_timeout_sec: 300
# Maximum number of completed executions to keep in memory.
# Oldest completed entries are evicted when this limit is exceeded.
max_execution_history: 100

# OpenAPI Documentation Endpoints
# Controls the /docs endpoints that serve OpenAPI capability descriptions
docs:
Expand Down
27 changes: 27 additions & 0 deletions src/ros2_medkit_gateway/design/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The following diagram shows the relationships between the main components of the
+ get_discovery_manager(): DiscoveryManager*
+ get_configuration_manager(): ConfigurationManager*
+ get_lock_manager(): LockManager*
+ get_script_manager(): ScriptManager*
}

class DiscoveryManager {
Expand Down Expand Up @@ -146,6 +147,19 @@ The following diagram shows the relationships between the main components of the
+ get_lock(): optional<LockInfo>
}

class ScriptManager {
+ set_backend(): void
+ has_backend(): bool
+ list_scripts(): expected<vector<ScriptInfo>, Error>
+ get_script(): expected<ScriptInfo, Error>
+ upload_script(): expected<ScriptUploadResult, Error>
+ delete_script(): expected<void, Error>
+ start_execution(): expected<ExecutionInfo, Error>
+ get_execution(): expected<ExecutionInfo, Error>
+ control_execution(): expected<ExecutionInfo, Error>
+ delete_execution(): expected<void, Error>
}

class NativeTopicSampler {
+ discover_all_topics(): vector<TopicInfo>
+ discover_topics(): vector<TopicInfo>
Expand Down Expand Up @@ -226,6 +240,7 @@ The following diagram shows the relationships between the main components of the
GatewayNode *-down-> OperationManager : owns
GatewayNode *-down-> ConfigurationManager : owns
GatewayNode *-down-> LockManager : owns
GatewayNode *-down-> ScriptManager : owns
GatewayNode *-down-> EntityCache : owns

' Discovery Manager uses Node interface
Expand All @@ -236,6 +251,7 @@ The following diagram shows the relationships between the main components of the
RESTServer --> DataAccessManager : uses
RESTServer --> OperationManager : uses
RESTServer --> ConfigurationManager : uses
RESTServer --> ScriptManager : uses

' OperationManager uses DiscoveryManager and native serialization
OperationManager --> DiscoveryManager : uses
Expand Down Expand Up @@ -361,10 +377,12 @@ Main Components
- Data endpoints: ``/components/{id}/data``, ``/components/{id}/data/{topic}``
- Operations endpoints: ``/apps/{id}/operations``, ``/apps/{id}/operations/{op}/executions``
- Configurations endpoints: ``/apps/{id}/configurations``, ``/apps/{id}/configurations/{param}``
- Scripts endpoints: ``/{entity_type}/{id}/scripts``, ``/{entity_type}/{id}/scripts/{script_id}/executions``
- Retrieves cached entities from the GatewayNode
- Uses DataAccessManager for runtime topic data access
- Uses OperationManager for service/action execution
- Uses ConfigurationManager for parameter CRUD operations
- Uses ScriptManager for script upload and execution
- Runs on configurable host and port with CORS support

5. **ConfigurationManager** - Manages ROS 2 node parameters
Expand Down Expand Up @@ -413,3 +431,12 @@ Main Components
- ``ServiceInfo`` - Service metadata (path, name, type)
- ``ActionInfo`` - Action metadata (path, name, type)
- ``EntityCache`` - Thread-safe cache of discovered entities (areas, components, apps)

10. **ScriptManager** - Manages diagnostic script upload, storage, and execution (SOVD 7.15)
- Delegates to a pluggable ``ScriptProvider`` backend (set via ``set_backend()``)
- Lists, uploads, and deletes scripts per entity
- Starts script executions as POSIX subprocesses with timeout support
- Tracks execution status (``prepared``, ``running``, ``completed``, ``failed``, ``terminated``)
- Supports termination via ``stop`` (SIGTERM) and ``forced_termination`` (SIGKILL)
- Built-in ``DefaultScriptProvider`` handles filesystem storage and manifest-defined scripts
- Supports concurrent execution limits and per-script timeout configuration
Loading
Loading