diff --git a/QUALITY_DECLARATION.md b/QUALITY_DECLARATION.md index f05ef325a..913f2c6ea 100644 --- a/QUALITY_DECLARATION.md +++ b/QUALITY_DECLARATION.md @@ -33,7 +33,7 @@ All packages in the repository share the same version number. ### Version Stability [1.ii] -The current version is **0.6.0**. +The current version is **0.7.0**. The package follows semver; the pre-1.0 version reflects that the public API may still evolve based on early adopter feedback, not a lack of quality infrastructure. The 1.0.0 release is planned after the API has been validated through pilot deployments. @@ -240,7 +240,7 @@ Security issues can be reported via GitHub Security Advisories on the | Requirement | Status | Notes | |---|---|---| -| Version policy | Met | Semver, all packages at 0.6.0 | +| Version policy | Met | Semver, all packages at 0.7.0 | | Stable version (>=1.0.0) | Caveat | Pre-1.0; API versioned, 1.0.0 planned post-pilot | | Change requests | Met | All changes via PR | | CI | Met | Build + test + coverage on every PR | @@ -252,6 +252,6 @@ Security issues can be reported via GitHub Security Advisories on the | Platform support | Met | Ubuntu Noble / ROS 2 Jazzy + Ubuntu Jammy / ROS 2 Humble + Ubuntu Resolute / ROS 2 Lyrical | | Security policy | Met | REP-2006 compliant | -**Caveat:** Version is 0.6.0 (pre-1.0.0, requirement 1.ii). The REST API is versioned (`/api/v1/`) +**Caveat:** Version is 0.7.0 (pre-1.0.0, requirement 1.ii). The REST API is versioned (`/api/v1/`) and the package meets all other Level 3 requirements. The 1.0.0 release is planned after API validation through pilot deployments. diff --git a/docs/Doxyfile b/docs/Doxyfile index 695f965be..721ce28ed 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -2,7 +2,7 @@ # Used by Breathe extension to integrate with Sphinx PROJECT_NAME = "ros2_medkit" -PROJECT_NUMBER = "0.6.0" +PROJECT_NUMBER = "0.7.0" PROJECT_BRIEF = "SOVD Gateway for ROS 2" # Input settings diff --git a/docs/api/rest.rst b/docs/api/rest.rst index 6cbb6c19f..0321f22fc 100644 --- a/docs/api/rest.rst +++ b/docs/api/rest.rst @@ -72,7 +72,7 @@ Server Capabilities { "name": "ROS 2 Medkit Gateway", - "version": "0.6.0", + "version": "0.7.0", "api_base": "/api/v1", "endpoints": [ "GET /api/v1/health", @@ -126,7 +126,7 @@ Server Capabilities "version": "1.0.0", "base_uri": "/api/v1", "vendor_info": { - "version": "0.6.0", + "version": "0.7.0", "name": "ros2_medkit" } } diff --git a/docs/changelog.rst b/docs/changelog.rst index 13ab432ae..3e47de24f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -38,6 +38,8 @@ This page aggregates changelogs from all ros2_medkit packages. .. include:: ../src/ros2_medkit_plugins/ros2_medkit_graph_provider/CHANGELOG.rst +.. include:: ../src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/CHANGELOG.rst + .. include:: ../src/ros2_medkit_plugins/ros2_medkit_opcua/CHANGELOG.rst .. include:: ../src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py index e218bae88..5338c8482 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,8 +42,8 @@ project_copyright = f'{datetime.now().year}, selfpatch' author = 'selfpatch Team' -version = '0.6.0' -release = '0.6.0' +version = '0.7.0' +release = '0.7.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/installation.rst b/docs/installation.rst index 80626094b..50c92cec9 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -41,10 +41,24 @@ for your distribution: vendored copy as a fallback - no manual installation is needed. The build system automatically uses the vendored header when the system package is insufficient. +Installation from Binary Packages +--------------------------------- + +ros2_medkit is in the ROS index, so most packages install with ``apt``: + +.. code-block:: bash + + sudo apt install ros-jazzy-ros2-medkit-gateway # or ros-humble- / ros-lyrical- + ros2 launch ros2_medkit_gateway bringup.launch.py + +``ros2_medkit_opcua`` is the one package not distributed this way; it is waiting +on open62541pp being vendored and has to be built from source. + Installation from Source ------------------------ -ros2_medkit is currently distributed as source code. Binary packages will be available in future releases. +Build from source to get every package, to run against an unreleased version, or +to develop against the tree. 1. **Create a workspace** diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 75195dfaa..1a91d25d8 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ros2-medkit-docs" -version = "0.6.0" +version = "0.7.0" description = "Documentation for ROS 2 Medkit" authors = [{ name = "bburda", email = "bartoszburda93@gmail.com" }] requires-python = ">=3.12" diff --git a/docs/requirements/specs/scripts.rst b/docs/requirements/specs/scripts.rst index 9d1ed4f61..aa7c8bc5e 100644 --- a/docs/requirements/specs/scripts.rst +++ b/docs/requirements/specs/scripts.rst @@ -3,49 +3,49 @@ Scripts .. req:: POST /{entity}/scripts :id: REQ_INTEROP_040 - :status: open + :status: verified :tags: Scripts The endpoint shall upload a new diagnostic script or test script to the addressed entity. .. req:: GET /{entity}/scripts :id: REQ_INTEROP_041 - :status: open + :status: verified :tags: Scripts The endpoint shall list all scripts stored on the addressed entity. .. req:: GET /{entity}/scripts/{id} :id: REQ_INTEROP_042 - :status: open + :status: verified :tags: Scripts The endpoint shall return metadata and content information for the addressed script. .. req:: DELETE /{entity}/scripts/{id} :id: REQ_INTEROP_043 - :status: open + :status: verified :tags: Scripts The endpoint shall delete the addressed script from the entity. .. req:: POST /{entity}/scripts/{id}/executions :id: REQ_INTEROP_044 - :status: open + :status: verified :tags: Scripts The endpoint shall start an execution of the addressed script on the entity. .. req:: GET /{entity}/scripts/{id}/executions/{exec-id} :id: REQ_INTEROP_046 - :status: open + :status: verified :tags: Scripts The endpoint shall return status and results for the addressed script execution. .. req:: PUT /{entity}/scripts/{id}/executions/{exec-id} :id: REQ_INTEROP_047 - :status: open + :status: verified :tags: Scripts The endpoint shall control or modify the addressed script execution, if supported. diff --git a/scripts/release.sh b/scripts/release.sh index 8bc6f838b..39fc74737 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -37,6 +37,13 @@ DOXYFILE="${REPO_ROOT}/docs/Doxyfile" QUALITY_DECL="${REPO_ROOT}/QUALITY_DECLARATION.md" REST_RST="${REPO_ROOT}/docs/api/rest.rst" +# Read one assignment out of docs/conf.py. Accepts either quote style and any +# spacing around "="; the value is anchored to the closing quote so a suffixed +# version is returned whole rather than truncated to its numeric prefix. +conf_py_field() { + grep -oP "^[[:space:]]*$1[[:space:]]*=[[:space:]]*[\"']\\K[^\"']*(?=[\"'][[:space:]]*\$)" "$CONF_PY" | head -1 +} + usage() { echo "Usage: $0 {bump |verify []}" echo "" @@ -98,9 +105,15 @@ cmd_bump() { # Update docs/conf.py version and release if [ -f "$CONF_PY" ]; then local old_conf - old_conf=$(grep -oP '^version = "\K[0-9]+\.[0-9]+\.[0-9]+' "$CONF_PY" || echo "unknown") - sed -i "s|^version = \"[0-9]\+\.[0-9]\+\.[0-9]\+\"|version = \"${target_version}\"|" "$CONF_PY" - sed -i "s|^release = \"[0-9]\+\.[0-9]\+\.[0-9]\+\"|release = \"${target_version}\"|" "$CONF_PY" + old_conf=$(conf_py_field version || echo "unknown") + sed -i -E "s|^([[:space:]]*version[[:space:]]*=[[:space:]]*)[\"'][^\"']*[\"']|\\1'${target_version}'|" "$CONF_PY" + sed -i -E "s|^([[:space:]]*release[[:space:]]*=[[:space:]]*)[\"'][^\"']*[\"']|\\1'${target_version}'|" "$CONF_PY" + # sed exits 0 whether or not it substituted, so confirm the rewrite landed + # rather than reporting a bump the file never received. + if [ "$(conf_py_field version)" != "${target_version}" ] || [ "$(conf_py_field release)" != "${target_version}" ]; then + echo " ERROR: docs/conf.py was not rewritten to ${target_version}" >&2 + return 1 + fi echo " docs/conf.py: ${old_conf} -> ${target_version}" fi @@ -187,10 +200,14 @@ cmd_verify() { # Check docs/conf.py if [ -f "$CONF_PY" ]; then - local conf_version - conf_version=$(grep -oP '^version = "\K[0-9]+\.[0-9]+\.[0-9]+' "$CONF_PY" || echo "unknown") + local conf_version conf_release + conf_version=$(conf_py_field version || echo "unknown") + conf_release=$(conf_py_field release || echo "unknown") if [ -n "$expected_version" ] && [ "$conf_version" != "$expected_version" ]; then - echo " MISMATCH: docs/conf.py is ${conf_version}, expected ${expected_version}" + echo " MISMATCH: docs/conf.py version is ${conf_version}, expected ${expected_version}" + all_ok=false + elif [ "$conf_release" != "$conf_version" ]; then + echo " MISMATCH: docs/conf.py release is ${conf_release}, version is ${conf_version}" all_ok=false else echo " OK: docs/conf.py = ${conf_version}" diff --git a/src/ros2_medkit_action_status_bridge/CHANGELOG.rst b/src/ros2_medkit_action_status_bridge/CHANGELOG.rst index 861ebc152..25158abf0 100644 --- a/src/ros2_medkit_action_status_bridge/CHANGELOG.rst +++ b/src/ros2_medkit_action_status_bridge/CHANGELOG.rst @@ -2,6 +2,13 @@ Changelog for package ros2_medkit_action_status_bridge ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* A deferred fault report is retried instead of dropped, and delivered promptly through a fast retry timer rather than waiting for the next ordinary cycle (`#472 `_) +* Silent parameter coercion is closed off, including a NaN that passed a ``FloatingPointRange`` descriptor and two integer narrowing paths where an out-of-range value wrapped back into the legal band before its own check ran. A value the node refuses or corrects is now reported rather than applied quietly (`#607 `_) +* Build and test only: the package is instrumented for coverage (`#582 `_), every integration launch test runs on its own DDS domain taken at run time (`#551 `_, `#597 `_), and the suite synchronises on bridge discovery before raising a fault instead of assuming the graph has settled (`#504 `_) +* Contributors: @bburda, @mfaferek93 + 0.6.0 (2026-06-22) ------------------ * Initial release: generic action-status bridge. Watches every diff --git a/src/ros2_medkit_action_status_bridge/package.xml b/src/ros2_medkit_action_status_bridge/package.xml index 8160d747e..9b448652b 100644 --- a/src/ros2_medkit_action_status_bridge/package.xml +++ b/src/ros2_medkit_action_status_bridge/package.xml @@ -2,7 +2,7 @@ ros2_medkit_action_status_bridge - 0.6.0 + 0.7.0 Bridge node turning terminal ROS2 action goal states (aborted) into FaultManager faults mfaferek93 diff --git a/src/ros2_medkit_cmake/CHANGELOG.rst b/src/ros2_medkit_cmake/CHANGELOG.rst index 545b5ed95..d96998b61 100644 --- a/src/ros2_medkit_cmake/CHANGELOG.rst +++ b/src/ros2_medkit_cmake/CHANGELOG.rst @@ -2,6 +2,15 @@ Changelog for package ros2_medkit_cmake ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* ``ROS_DOMAIN_ID`` isolation is allocated at run time instead of from a hand-maintained table. A test registered through ``medkit_add_gtest``, ``medkit_add_gmock``, ``medkit_add_launch_test``, ``medkit_add_pytest_test`` or ``medkit_add_wrapped_test`` takes a domain when it starts and holds it through an open socket for exactly as long as it runs, so a crash or a SIGKILL releases it the same way an ordinary exit does. The isolation reaches across packages, which a CTest ``RESOURCE_LOCK`` cannot, because colcon runs one ctest per package in parallel. ``medkit_test_needs_no_domain()`` opts out a test that creates no ROS node. The usable band excludes the domains whose RTPS port slice falls inside the kernel ephemeral port range (`#551 `_, `#597 `_) +* Launch tests are no longer registered in a system package build, where they failed as a group and were reported as passing anyway because bloom runs the test step permissively; the macro now owns their properties instead of each package setting them (`#608 `_, `#602 `_) +* clang-tidy analyses a package's translation units in parallel, with the job count capped for an 8 GB machine and packages serialised so the cap holds, cutting PR feedback time from about 56 minutes to about 35 (`#588 `_, `#590 `_) +* Every C++ package is instrumented for coverage, and every package that lints exports a compile database (`#582 `_) +* Sanitizer builds keep their asserts, and the build reports what ccache did (`#590 `_) +* Contributors: @bburda + 0.6.0 (2026-06-22) ------------------ * ``ROS2MedkitTestDomain``: carve dedicated ``ROS_DOMAIN_ID`` ranges for the new log bridge (210-214) and action-status bridge (215-219) test suites out of the integration-tests range (`#422 `_) diff --git a/src/ros2_medkit_cmake/package.xml b/src/ros2_medkit_cmake/package.xml index f2870c97f..61ba8c85e 100644 --- a/src/ros2_medkit_cmake/package.xml +++ b/src/ros2_medkit_cmake/package.xml @@ -2,7 +2,7 @@ ros2_medkit_cmake - 0.6.0 + 0.7.0 Shared CMake modules for ros2_medkit packages (multi-distro compat, ccache, linting) bburda Apache-2.0 diff --git a/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst b/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst index 5d6ddc2ee..421327043 100644 --- a/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst +++ b/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog for package ros2_medkit_diagnostic_bridge ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* The ``hardware_id`` of a diagnostic message is passed through as the fault's source id, so a fault raised from ``/diagnostics`` is attributed to the device the publisher named rather than to the bridge (`#622 `_) +* A fault code can be extracted from a diagnostic message's key/value attributes, so a publisher that already carries its own code no longer has to encode it in the message name (`#527 `_) +* The Humble fallback warning builds again (`#622 `_) +* Build and test only: the package is instrumented for coverage (`#582 `_), every integration launch test runs on its own DDS domain taken at run time (`#551 `_, `#597 `_), and the suite re-emits and polls until the fault surfaces instead of asserting once against a graph that may not have settled (`#504 `_) +* Contributors: @bburda, @mfaferek93, @nnarain + 0.6.0 (2026-06-22) ------------------ * Tests: label ``test_integration`` as an integration test so it runs in the integration suite instead of the unit set (`#443 `_) diff --git a/src/ros2_medkit_diagnostic_bridge/package.xml b/src/ros2_medkit_diagnostic_bridge/package.xml index 9e981abb9..8dea6b813 100644 --- a/src/ros2_medkit_diagnostic_bridge/package.xml +++ b/src/ros2_medkit_diagnostic_bridge/package.xml @@ -2,7 +2,7 @@ ros2_medkit_diagnostic_bridge - 0.6.0 + 0.7.0 Bridge node converting ROS2 /diagnostics to FaultManager faults mfaferek93 diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/CHANGELOG.rst b/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/CHANGELOG.rst index c676f9a07..c81899a7f 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/CHANGELOG.rst +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package ros2_medkit_beacon_common ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Build and test only: the package is instrumented for coverage (`#582 `_), and its tests take a DDS domain at run time from the shared allocator instead of a hand-maintained per-package pool (`#597 `_) +* Contributors: @bburda + 0.6.0 (2026-06-22) ------------------ * No functional changes; version bump for the coordinated 0.6.0 release. diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/package.xml b/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/package.xml index 1f7447976..348e8b849 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/package.xml +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_beacon_common/package.xml @@ -2,7 +2,7 @@ ros2_medkit_beacon_common - 0.6.0 + 0.7.0 Shared library for ros2_medkit beacon discovery plugins bburda diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/CHANGELOG.rst b/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/CHANGELOG.rst index 9e3818ead..93243bd4c 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/CHANGELOG.rst +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog for package ros2_medkit_linux_introspection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Container CPU and memory limits are reported on every common cgroup layout, not just one. The reader parses the cgroup v1 line format alongside v2, and resolves the limit files under both ``cgroupns=host`` and ``cgroupns=private`` - the latter being the Docker default, where the container sees its own cgroup mounted directly at ``/sys/fs/cgroup`` and the previously built path did not exist. A limit that could not be read is now distinguished from a container that genuinely has no limit, instead of both being reported as unlimited (`#637 `_, `#604 `_) +* Limits are read from the cgroup that owns them rather than from the process's own leaf, a legacy controller's limit is no longer outranked by a hierarchy that does not set one, and a limit file is read to its end instead of to the first short read (`#637 `_) +* Containers are detected per process rather than once for the whole node, and the ctest suite exercises every supported cgroup layout against synthetic hierarchies (`#637 `_) +* Build and test only: the package is instrumented for coverage (`#582 `_), and its tests are opted out of DDS domain allocation, reading synthetic /proc and cgroup trees rather than creating a ROS node (`#597 `_) +* Contributors: @bburda + 0.6.0 (2026-06-22) ------------------ * No functional changes; version bump for the coordinated 0.6.0 release. diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/package.xml b/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/package.xml index 45463b941..561637b47 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/package.xml +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_linux_introspection/package.xml @@ -2,7 +2,7 @@ ros2_medkit_linux_introspection - 0.6.0 + 0.7.0 Linux introspection plugins for ros2_medkit gateway - procfs, systemd, and container bburda Apache-2.0 diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/CHANGELOG.rst b/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/CHANGELOG.rst index b59b2d072..51128e778 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/CHANGELOG.rst +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package ros2_medkit_param_beacon ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Build and test only: every integration launch test runs on its own DDS domain, taken when the test starts and released when it ends, so a crash frees the domain the same way an ordinary exit does (`#551 `_, `#597 `_). The usable band excludes the domains whose RTPS port slice falls inside the kernel ephemeral port range, where any process on the machine can steal a port and kill a node with a bind failure +* Build: the package is instrumented for coverage (`#582 `_) +* Contributors: @bburda + 0.6.0 (2026-06-22) ------------------ * No functional changes; version bump for the coordinated 0.6.0 release. diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/package.xml b/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/package.xml index 62e87402c..a39c06ba4 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/package.xml +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_param_beacon/package.xml @@ -2,7 +2,7 @@ ros2_medkit_param_beacon - 0.6.0 + 0.7.0 Parameter-based beacon discovery plugin for ros2_medkit gateway bburda diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/CHANGELOG.rst b/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/CHANGELOG.rst index dae6428d9..b9f451a4b 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/CHANGELOG.rst +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package ros2_medkit_topic_beacon ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Build and test only: every integration launch test runs on its own DDS domain, taken when the test starts and released when it ends, so a crash frees the domain the same way an ordinary exit does (`#551 `_, `#597 `_). The usable band excludes the domains whose RTPS port slice falls inside the kernel ephemeral port range, where any process on the machine can steal a port and kill a node with a bind failure +* Build: the package is instrumented for coverage (`#582 `_) +* Contributors: @bburda + 0.6.0 (2026-06-22) ------------------ * No functional changes; version bump for the coordinated 0.6.0 release. diff --git a/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/package.xml b/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/package.xml index dcef718c7..4a1953502 100644 --- a/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/package.xml +++ b/src/ros2_medkit_discovery_plugins/ros2_medkit_topic_beacon/package.xml @@ -2,7 +2,7 @@ ros2_medkit_topic_beacon - 0.6.0 + 0.7.0 Topic-based beacon discovery plugin for ros2_medkit gateway bburda diff --git a/src/ros2_medkit_fault_detection/CHANGELOG.rst b/src/ros2_medkit_fault_detection/CHANGELOG.rst index 1f92dc33c..a12c26b68 100644 --- a/src/ros2_medkit_fault_detection/CHANGELOG.rst +++ b/src/ros2_medkit_fault_detection/CHANGELOG.rst @@ -2,9 +2,9 @@ Changelog for package ros2_medkit_fault_detection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -0.6.0 (2026-06-22) +0.7.0 (2026-08-27) ------------------ -* Initial release: shared, protocol-agnostic fault-detection model for medkit +* Initial release of the package: shared, protocol-agnostic fault-detection model for medkit gateway plugins. A single header-only evaluator maps a raw value read from any source (OPC UA, S7, Modbus, ADS, ...) into the set of faults it implies, using one of three composable detection modes: ``ThresholdRule`` (numeric @@ -25,4 +25,7 @@ Changelog for package ros2_medkit_fault_detection * Shipped as a header-only INTERFACE library (``cxx_std_17``); the ``OPC UA`` plugin is the first consumer and migrates its threshold / status-bit / enum detection onto this module. +* Global fault-code uniqueness is enforced across an OPC UA node map, so two rules cannot claim the same code and leave which one raised it undefined (`#486 `_) +* A tracker holds its prior state on an undecidable read instead of clearing a standing fault, and an enum value with no mapping is labelled rather than dropped (`#486 `_) +* Build and test only: the package is instrumented for coverage (`#582 `_), and its test is registered through the shared macros and opted out of DDS domain allocation, the evaluator being pure logic with no ROS node (`#597 `_) * Contributors: @mfaferek93, @bburda diff --git a/src/ros2_medkit_fault_detection/package.xml b/src/ros2_medkit_fault_detection/package.xml index 195093ca6..2f72c73a9 100644 --- a/src/ros2_medkit_fault_detection/package.xml +++ b/src/ros2_medkit_fault_detection/package.xml @@ -2,7 +2,7 @@ ros2_medkit_fault_detection - 0.6.0 + 0.7.0 Shared, protocol-agnostic fault-detection model (threshold, status-bit, fault-code enum) for medkit gateway plugins mfaferek93 diff --git a/src/ros2_medkit_fault_manager/CHANGELOG.rst b/src/ros2_medkit_fault_manager/CHANGELOG.rst index 9332f6e62..078574676 100644 --- a/src/ros2_medkit_fault_manager/CHANGELOG.rst +++ b/src/ros2_medkit_fault_manager/CHANGELOG.rst @@ -2,16 +2,27 @@ Changelog for package ros2_medkit_fault_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ -* Rosbag black-box recordings are no longer limited to one per fault code. A fault that re-confirms keeps a bounded history of recordings instead of overwriting the previous one, controlled by the new ``snapshots.rosbag.max_bags_per_fault`` (default ``1``, which reproduces the previous behaviour exactly; ``0`` = unlimited). Retention is keep-newest and the bag is unlinked only when no fault still references it, so a burst that shares one recording behaves as before. Internally the ``rosbag_files`` grain changed from "one row per fault" to "one row per (fault, recording) link": ``recording_id`` is now a stored, indexed column, and the legacy column-level ``UNIQUE(fault_code)`` is replaced by a ``UNIQUE INDEX`` on ``(fault_code, file_path)`` through an automatic, idempotent table rebuild on first open. Four latent defects are fixed on the way: quota eviction deleted by fault code rather than by recording, ``get_rosbag_file`` had no ``ORDER BY`` and would have served an arbitrary recording, the stale-row self-heals deleted a fault's entire history because one bag had vanished from disk, and both ``delete_rosbag_file`` / ``delete_rosbag_files`` read only the first ``file_path`` of a fault, so deleting a fault with several recordings removed every row but left all but one bag on disk - unreachable and still charged against the quota (`#620 `_) -* Optional append-only, hash-chained audit log of fault state transitions: each transition appends one immutable row (``record_hash = sha256(prev_hash + canonical(event))`` via OpenSSL EVP SHA-256) with a persisted chain head, a ``verify`` routine, a read API, and retention that seals a segment anchor before pruning. Time-based (PREFAILED->CONFIRMED) auto-confirmations are also audited. ``verify`` reads the chain head directly from the database, so deleting the newest row together with the head row is reported as tampering instead of silently recovering. ``BEFORE UPDATE`` / ``BEFORE DELETE`` triggers reject out-of-band edits as defense-in-depth. The chain is unkeyed and stored in a single writable file, so ``verify`` detects edits/deletions that did not recompute the chain (casual or accidental tampering); it is not a defence against an attacker who can rewrite the whole file. Off by default (`#483 `_) +0.7.0 (2026-08-27) +------------------ +* Rosbag black-box recordings are no longer limited to one per fault code. A fault that re-confirms keeps a bounded history of recordings instead of overwriting the previous one, controlled by the new ``snapshots.rosbag.max_bags_per_fault`` (default ``1``, which reproduces the previous behaviour exactly; ``0`` = unlimited). Retention is keep-newest and the bag is unlinked only when no fault still references it, so a burst that shares one recording behaves as before. Internally the ``rosbag_files`` grain changed from "one row per fault" to "one row per (fault, recording) link": ``recording_id`` is now a stored, indexed column, and the legacy column-level ``UNIQUE(fault_code)`` is replaced by a ``UNIQUE INDEX`` on ``(fault_code, file_path)`` through an automatic, idempotent table rebuild on first open. Four latent defects are fixed on the way: quota eviction deleted by fault code rather than by recording, ``get_rosbag_file`` had no ``ORDER BY`` and would have served an arbitrary recording, the stale-row self-heals deleted a fault's entire history because one bag had vanished from disk, and both ``delete_rosbag_file`` / ``delete_rosbag_files`` read only the first ``file_path`` of a fault, so deleting a fault with several recordings removed every row but left all but one bag on disk - unreachable and still charged against the quota (`#623 `_, `#620 `_) +* Optional append-only, hash-chained audit log of fault state transitions: each transition appends one immutable row (``record_hash = sha256(prev_hash + canonical(event))`` via OpenSSL EVP SHA-256) with a persisted chain head, a ``verify`` routine, a read API, and retention that seals a segment anchor before pruning. Time-based (PREFAILED->CONFIRMED) auto-confirmations are also audited. ``verify`` reads the chain head directly from the database, so deleting the newest row together with the head row is reported as tampering instead of silently recovering. ``BEFORE UPDATE`` / ``BEFORE DELETE`` triggers reject out-of-band edits as defense-in-depth. The chain is unkeyed and stored in a single writable file, so ``verify`` detects edits/deletions that did not recompute the chain (casual or accidental tampering); it is not a defence against an attacker who can rewrite the whole file. Off by default (`#487 `_, `#483 `_) +* **Breaking:** the default rosbag storage format is ``mcap`` again. ``snapshots.rosbag.format`` now defaults to ``"mcap"``, so black-box recordings land as ``.mcap`` files instead of ``.db3``, and the filename a bulk-data download serves changes with them. The storage plugins are declared explicitly and the plugin loader is serialised, which is what makes the format selectable reliably rather than dependent on load order. Set ``snapshots.rosbag.format: sqlite3`` to keep the previous on-disk format (`#610 `_) +* Freeze-frame: a compact JSON snapshot of the entity's data is persisted when a fault is confirmed, so the state at the moment of confirmation survives the fault being cleared (`#491 `_) +* A fault that confirms inside an active post-roll window keeps its black-box recording instead of finding the buffer already finalised (`#561 `_), and a fault landing on a recording-window boundary gets its own bag rather than none (`#594 `_) +* The debounce counter is clamped and the confirmed / healed status is latched, so a counter cannot run past its threshold and a status cannot silently regress (`#484 `_) +* A PASSED event no longer re-dates a fault - ``first_occurred`` keeps marking the start of the current occurrence - and genuine SSE loss is counted rather than absorbed (`#573 `_) +* The near-miss series survives a fault being cleared, so acknowledging a fault no longer discards the evidence gathered around it (`#629 `_) +* The fault manager's YAML parameter file supports launch substitutions, so a path can be composed at launch time instead of being fixed in the file (`#634 `_) +* Build and test only: the package is instrumented for coverage (`#582 `_), every launch test lives under ``test/integration`` and takes a DDS domain at run time (`#628 `_, `#551 `_, `#597 `_) +* A ``fault_code`` up to the advertised 256 characters is accepted, where the manager previously stopped at 128, and a long code no longer costs the recording: the rosbag filename is truncated with a digest instead of exceeding the filesystem's component limit (`#591 `_) +* Contributors: @bburda, @mfaferek93, @nnarain 0.6.0 (2026-06-22) ------------------ * Bounded concurrent snapshot capture under fault storms with a ``CaptureThreadPool`` and configurable capture pool / queue / overflow-policy parameters. The rosbag leg is serialized and the cooldown map is bounded, so a burst of simultaneous faults can no longer exhaust capture threads or grow memory without limit (`#456 `_) * Entity-scoped rosbag capture by default (`#431 `_) * Made rosbag capture enablement crash-safe (`#430 `_) +* The default rosbag storage format moved back from ``mcap`` to ``sqlite3``, which ships with rosbag2 and needs no extra package; ``mcap`` stayed selectable through ``snapshots.rosbag.format``. This came in with the crash-safety fix above and was not recorded at the time (`#430 `_) * Contributors: @bburda, @mfaferek93 0.5.0 (2026-06-08) diff --git a/src/ros2_medkit_fault_manager/package.xml b/src/ros2_medkit_fault_manager/package.xml index 7c217f04f..445d17ec9 100644 --- a/src/ros2_medkit_fault_manager/package.xml +++ b/src/ros2_medkit_fault_manager/package.xml @@ -2,7 +2,7 @@ ros2_medkit_fault_manager - 0.6.0 + 0.7.0 Central fault manager node for ros2_medkit fault management system bburda diff --git a/src/ros2_medkit_fault_reporter/CHANGELOG.rst b/src/ros2_medkit_fault_reporter/CHANGELOG.rst index 4ee219e10..e9724fd6a 100644 --- a/src/ros2_medkit_fault_reporter/CHANGELOG.rst +++ b/src/ros2_medkit_fault_reporter/CHANGELOG.rst @@ -2,10 +2,11 @@ Changelog for package ros2_medkit_fault_reporter ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ -* ``FaultReporter`` can now be constructed from an ``rclcpp_lifecycle::LifecycleNode`` (and from a plain ``rclcpp::Node`` reference or explicit node interfaces), enabling use inside lifecycle nodes (`#555 `_) -* Contributors: @zeerekahmad +0.7.0 (2026-08-27) +------------------ +* ``FaultReporter`` can now be constructed from an ``rclcpp_lifecycle::LifecycleNode`` (and from a plain ``rclcpp::Node`` reference or explicit node interfaces), enabling use inside lifecycle nodes (`#556 `_, `#555 `_) +* Build and test only: the package is instrumented for coverage (`#582 `_), and every test takes a DDS domain at run time from the shared allocator (`#551 `_, `#597 `_) +* Contributors: @bburda, @mfaferek93, @zeerekahmad 0.6.0 (2026-06-22) ------------------ diff --git a/src/ros2_medkit_fault_reporter/package.xml b/src/ros2_medkit_fault_reporter/package.xml index e539d5214..bb91df18d 100644 --- a/src/ros2_medkit_fault_reporter/package.xml +++ b/src/ros2_medkit_fault_reporter/package.xml @@ -2,7 +2,7 @@ ros2_medkit_fault_reporter - 0.6.0 + 0.7.0 Client library for easy fault reporting with local filtering mfaferek93 diff --git a/src/ros2_medkit_gateway/CHANGELOG.rst b/src/ros2_medkit_gateway/CHANGELOG.rst index de82f8d0f..6c4dd39ee 100644 --- a/src/ros2_medkit_gateway/CHANGELOG.rst +++ b/src/ros2_medkit_gateway/CHANGELOG.rst @@ -2,10 +2,39 @@ Changelog for package ros2_medkit_gateway ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ -* Rosbag bulk-data is addressed by recording id instead of fault code, so a fault holding several recordings can expose each one. ``GET /{entity}/bulk-data/rosbags`` now emits one descriptor per recording rather than one per fault - a burst that shares a bag used to appear as several entries each reporting the full bag size - and the covered faults move into ``x-medkit.fault_codes`` (was the scalar ``x-medkit.fault_code``). Old URLs keep working: an id that is not a recording is resolved as a fault code and serves that fault's newest recording, which is what it returned before. Authorization is unchanged in effect - a download is allowed when any fault the recording covers is in the entity's source scope, which is exactly the set that could reach it previously (`#620 `_) -* Manual asset inventory: a manifest ``assets:`` list and a new ``discovery.inventory.csv_path`` parameter declare assets that no protocol layer can describe (or fully describe). Both paths recognize the canonical names ``id, manufacturer, model, serial, hardware_rev, firmware, endpoint, role, area`` plus the shared aliases (``serial_number``, ``hardware_revision`` / ``hw_rev``, ``firmware_version`` / ``fw``) and keep any other column / key as an extra; RFC-4180-style quoting is honored. Each asset becomes a Component with ``source = "inventory"`` and a structured asset identity carrying per-field provenance, appended to the base manifest on every load / reload and merged into the tree by id alongside protocol-discovered structure; ``area`` places the asset under an Area, without it the asset appears only in the flat component list. CSV rows never fail the load: rows without an ``id`` are skipped with a warning, for duplicate ids the first row wins, a row whose id is already a manifest component keeps the manifest definition (the row's identity is folded in as gap-fill), and an unknown ``area`` is dropped with a warning. The CSV is size-capped at 1 MiB before being read; a missing file is skipped with a warning (mirrors ``fragments_dir``), while an unreadable or malformed one fails the load / reload. Requires a manifest-backed discovery mode (``manifest_only`` / ``hybrid`` with ``discovery.manifest_path`` set); empty = disabled (default) (`#490 `_) +0.7.0 (2026-08-27) +------------------ +* Rosbag bulk-data is addressed by recording id instead of fault code, so a fault holding several recordings can expose each one. ``GET /{entity}/bulk-data/rosbags`` now emits one descriptor per recording rather than one per fault - a burst that shares a bag used to appear as several entries each reporting the full bag size - and the covered faults move into ``x-medkit.fault_codes`` (was the scalar ``x-medkit.fault_code``). Old URLs keep working: an id that is not a recording is resolved as a fault code and serves that fault's newest recording, which is what it returned before. Authorization is unchanged in effect - a download is allowed when any fault the recording covers is in the entity's source scope, which is exactly the set that could reach it previously (`#623 `_, `#620 `_) +* Manual asset inventory: a manifest ``assets:`` list and a new ``discovery.inventory.csv_path`` parameter declare assets that no protocol layer can describe (or fully describe). Both paths recognize the canonical names ``id, manufacturer, model, serial, hardware_rev, firmware, endpoint, role, area`` plus the shared aliases (``serial_number``, ``hardware_revision`` / ``hw_rev``, ``firmware_version`` / ``fw``) and keep any other column / key as an extra; RFC-4180-style quoting is honored. Each asset becomes a Component with ``source = "inventory"`` and a structured asset identity carrying per-field provenance, appended to the base manifest on every load / reload and merged into the tree by id alongside protocol-discovered structure; ``area`` places the asset under an Area, without it the asset appears only in the flat component list. CSV rows never fail the load: rows without an ``id`` are skipped with a warning, for duplicate ids the first row wins, a row whose id is already a manifest component keeps the manifest definition (the row's identity is folded in as gap-fill), and an unknown ``area`` is dropped with a warning. The CSV is size-capped at 1 MiB before being read; a missing file is skipped with a warning (mirrors ``fragments_dir``), while an unreadable or malformed one fails the load / reload. Requires a manifest-backed discovery mode (``manifest_only`` / ``hybrid`` with ``discovery.manifest_path`` set); empty = disabled (default) (`#493 `_, `#490 `_) +* **Breaking:** the lifecycle status ``operationId`` values were singularized - ``getAppStatus`` and ``putAppStatusRestart`` rather than the plural collection forms they were built from before - so a generated client gets renamed methods for those operations (`#497 `_) +* Aggregation now separates the time budget for reading metadata from the budget for real work. One ``aggregation.timeout_ms`` was applied as both the connect and the read timeout for every call, so a synchronous service call on a peer got two seconds end to end while the peer's own budget for the same call was ten; a large resource fanned out to a peer could not finish inside it either. The write timeout, which was never set and stayed at the cpp-httplib default, now follows the configured budget, and the timeout values are validated and reported rather than silently clamped (`#638 `_, `#528 `_) +* A request that ran out of time is reported as a timeout. A peer that did not answer in the budget returns ``504`` with ``ERR_NOT_RESPONDING`` instead of ``502`` claiming the peer is unavailable - which it did while that peer was answering the same request - a fanned-out collection carries the per-peer failure reason instead of only a boolean, and an operation that exceeds its own service-call budget says so rather than returning a generic failure (`#638 `_, `#612 `_) +* ``GET /faults/stream`` on an aggregating gateway relays its peers' fault events. It previously returned ``200`` with an open stream that only ever sent keepalive comments, which is indistinguishable from a healthy system - and on a deployment where the aggregator is the only reachable port, it was the only fault stream available (`#638 `_, `#611 `_) +* One addressing model for an aggregating entity's resources, so the aggregator no longer refuses or 404s work its peers can serve (`#626 `_, `#613 `_) +* Nested ``plugins..*`` parameters are rebuilt into a nested object instead of a flat dotted key, so nested plugin configuration reaches the plugin again (`#518 `_, `#520 `_) +* Discovery configuration is read from the documented top-level ``config:`` key. It was only ever read from ``discovery.config``, so the documented form was dropped without a word and unmanifested nodes leaked into the tree in hybrid mode (`#609 `_, `#529 `_) +* Every startup parameter that is coerced or refused is reported. A clamped thread count or keep-alive timeout used to change the value and log nothing, leaving the configuration file and the running process in silent disagreement. Integer parameters are read as the int64 a ROS parameter holds and validated before narrowing, so a value past ``INT_MAX`` can no longer wrap back into the legal band and pass its own range check, and range checks are written so that NaN is refused rather than accepted (`#607 `_, `#603 `_) +* ``server.executor_threads`` is real rather than advisory, and a cancel that runs out of time is reported as a timeout (`#593 `_) +* An unresponsive parameter node no longer hangs the REST API (`#532 `_), per-node parameter caches are bounded with LRU eviction (`#534 `_), the transport's node is released before the context dies (`#567 `_), and the parameter error surface is consistent between list and get, with a non-404 error winning over NOT_FOUND across nodes (`#540 `_, `#543 `_) +* Faults from plugin-provided entities are visible in the fault list, in fault detail and in freeze-frame through fault-scope ownership (`#503 `_), an external Component owns its fault-manager faults (`#530 `_), plugin-provided entities own their bulk data and logs (`#560 `_), and the external flag survives the hybrid merge (`#522 `_) +* Zero-config freeze-frame for plugin-backed entities: when a fault confirms with a plugin-owned reporting source, that entity's current data values are snapshotted with no configuration. Plugin entities report under their bare SOVD entity id and their values are not ROS topics, so the fault manager's own snapshot capture could never reach them. Gated on at least one plugin being loaded (`#538 `_), a fault already raised at gateway startup gets one (`#563 `_), and so does an entity serving last known values (`#565 `_) +* Data triggers work on plugin-provided entities, resolving from declared topics, and fail loudly on a topic name that cannot be resolved instead of silently never firing (`#592 `_). Trigger subscriptions go through the shared subscription executor (`#549 `_) +* Asset identity model with per-field provenance and merge-by-identity (`#488 `_) +* Build, image and test: an arm64 multi-arch image gated on tag or dispatch (`#508 `_), the fault bridges and ``ros2_medkit_fault_detection`` bundled into the image (`#470 `_, `#494 `_), coverage instrumentation for every C++ package (`#582 `_), and clang-tidy analysing a package's translation units in parallel, cutting PR feedback time from about 56 minutes to about 35 (`#588 `_, `#590 `_) +* Config-less fault triggers: a threshold rule declared at runtime raises a fault when a data value crosses it and clears the fault when the value comes back. Rules are managed on Apps over ``GET`` and ``POST`` ``/apps/{app_id}/fault-triggers`` and ``DELETE`` ``/apps/{app_id}/fault-triggers/{trigger_id}``, and survive a restart when ``fault_triggers.storage.path`` names a database. The engine needs at least one loaded plugin, so ``fault_triggers.enabled`` (default true) is necessary rather than sufficient; ``fault_triggers.poll_interval_ms`` (default 1000) is floored at 50 ms and a lower value is reported. This is a separate facility from the SOVD notification ``/triggers`` (`#544 `_) +* An aggregating gateway can authenticate to its peers. ``aggregation.peer_auth_header`` carries the credential this gateway presents on connections it opens on its own behalf - the peer health check, the entity fetch, the fault-stream relay, and any forward whose caller sent no credential to pass on. Where ``forward_auth`` is enabled and the caller did send one, that token wins, so the peer keeps being told the end user. The header is empty by default, and is deliberately withheld from peers found by mDNS discovery rather than configured explicitly. The value is redacted from the configurations API alongside ``auth.jwt_secret`` and ``auth.clients``, so reading configuration back cannot disclose it (`#638 `_) +* A secure-by-default field profile ships as ``gateway_params.secure.yaml``, together with a hardening checklist. It turns on JWT authentication, TLS, restricted CORS and rate limiting, so it needs certificates and credentials provisioned before use and is not a drop-in replacement for the default profile (`#485 `_) +* The fault SSE stream carries ``auto_cleared_codes``. A consumer can now see which correlated symptom faults were cleared along with their root cause, which previously happened without any event of their own (`#573 `_) +* **Breaking:** an action execution now belongs to the entity it was started on. Reading, stopping or cancelling an execution through a different entity returns ``404`` instead of being served, and an execution listing is filtered to the executions the addressed entity owns. An execution id used to work as a global handle (`#591 `_) +* **Breaking:** ``GET /docs`` is readable by ``viewer`` rather than ``admin``. The permission table is now derived from the route registrations instead of a hand-maintained literal, and this route's derived pattern places it with the other read paths. Only reachable where ``auth.enabled`` is true (`#591 `_) +* The OpenAPI document is derived from the code that serves the routes rather than declared beside it: the success status and its schema come from the handler's return type, the ``Location`` header follows from that status, a feature gate declares the ``501`` it returns, the lock contract and the RBAC permission table are read out of the registrations, and each ``/docs`` sub-document is a projection of the paths that path actually serves. The rule and what is deliberately still declared by hand are written down in ``design/openapi_derivation.rst`` (`#591 `_, `#583 `_) +* A build with ``BUILD_TESTING=ON`` records every status the gateway puts on the wire, and an integration sweep driven from the served document asserts that each one is declared. The sweep refuses to pass vacuously: operations it could not reach must match a declared list, and it requires a minimum spread of error sites. The recorder is compiled out of the published image (`#591 `_) +* Statuses a caller receives, corrected: a configuration value that cannot be converted to the parameter's ROS type answers ``400`` rather than ``500``; a fault manager that refuses a fault lookup or clear answers ``404``, with ``503`` reserved for a transport that gave no answer at all; a ``config_id`` past the published bound answers ``400`` on ``DELETE`` as it already did on the other verbs; and an operation-execution listing resolves on Areas and Functions instead of reporting ``404`` (`#591 `_) +* ``POST`` on triggers, cyclic subscriptions and fault triggers returns a usable ``Location``, and every ``Location`` is canonicalised - a request with a trailing slash used to yield a URI that answered ``404`` (`#591 `_) +* Entity detail responses advertise ``data-categories`` and ``data-groups`` on every entity type, ``locks`` on components and apps where locking is configured, and ``fault-triggers`` on apps; the ``capabilities`` array now lists the collections each entity type actually serves. Two operation ids are added, ``getCapabilityDescription`` and ``getScopedCapabilityDescription``; no existing operation id was renamed and no route was added to or removed from the served API (`#591 `_) +* The root endpoint list includes plugin-mounted routes, and an SSE cyclic-subscription error frame carries ``vendor_code`` alongside its ``error_code`` (`#591 `_) +* In-flight update tasks are drained before their notifier is destroyed, closing a shutdown race (`#591 `_) +* Contributors: @bburda, @mfaferek93, @YueBit 0.6.0 (2026-06-22) ------------------ diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/version.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/version.hpp index 0090a454c..fdd86db77 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/version.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/version.hpp @@ -21,7 +21,7 @@ namespace ros2_medkit_gateway { #ifdef GATEWAY_VERSION_STRING constexpr const char * kGatewayVersion = GATEWAY_VERSION_STRING; #else -constexpr const char * kGatewayVersion = "0.6.0"; +constexpr const char * kGatewayVersion = "0.7.0"; #endif /// SOVD specification version diff --git a/src/ros2_medkit_gateway/package.xml b/src/ros2_medkit_gateway/package.xml index 78a5f8e40..47d9c3479 100644 --- a/src/ros2_medkit_gateway/package.xml +++ b/src/ros2_medkit_gateway/package.xml @@ -2,7 +2,7 @@ ros2_medkit_gateway - 0.6.0 + 0.7.0 HTTP gateway for ros2_medkit diagnostics system bburda diff --git a/src/ros2_medkit_integration_tests/CHANGELOG.rst b/src/ros2_medkit_integration_tests/CHANGELOG.rst index f4d99cd94..f9b215546 100644 --- a/src/ros2_medkit_integration_tests/CHANGELOG.rst +++ b/src/ros2_medkit_integration_tests/CHANGELOG.rst @@ -2,6 +2,15 @@ Changelog for package ros2_medkit_integration_tests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Every integration launch test runs on its own DDS domain, taken when the test starts and held through an open socket for exactly as long as it runs, so a crash or a SIGKILL releases it the same way an ordinary exit does. This replaces the hand-maintained per-package domain pools, which had to stay pairwise disjoint because colcon runs one ctest per package in parallel and a CTest ``RESOURCE_LOCK`` binds only inside a single ctest run (`#551 `_, `#597 `_) +* Feature tests are registered by glob: a file dropped into ``test/features/*.test.py`` is registered with its port and domain assigned automatically, so no explicit registration is needed and none can bypass ``GATEWAY_TEST_PORT`` +* Inline gateways and fault managers go through the shared launch helpers rather than each test hand-building its own, which also fixed the SIGKILL these tests hit under coverage instrumentation (`#554 `_) +* The Python in this package is linted (`#598 `_), and the package is instrumented for coverage (`#582 `_) +* Flaky suites are fixed at their cause rather than retried: the OPC UA Alarms and Conditions test, the action-status and diagnostic-bridge fault paths, the operation-handlers fixture, ``type_info`` caching and the documentation linkcheck (`#504 `_, `#636 `_) +* Contributors: @bburda, @mfaferek93, @YueBit + 0.6.0 (2026-06-22) ------------------ * New suites covering the SOVD entity status endpoints (REQ_INTEROP_076), lifecycle-aware app and component status (`#455 `_), and fault-storm capture liveness under bounded concurrency (`#456 `_) diff --git a/src/ros2_medkit_integration_tests/package.xml b/src/ros2_medkit_integration_tests/package.xml index 4509690f8..6abce50ed 100644 --- a/src/ros2_medkit_integration_tests/package.xml +++ b/src/ros2_medkit_integration_tests/package.xml @@ -2,7 +2,7 @@ ros2_medkit_integration_tests - 0.6.0 + 0.7.0 Integration tests and demo nodes for ros2_medkit bburda diff --git a/src/ros2_medkit_log_bridge/CHANGELOG.rst b/src/ros2_medkit_log_bridge/CHANGELOG.rst index 7a0bc8f52..db3146a35 100644 --- a/src/ros2_medkit_log_bridge/CHANGELOG.rst +++ b/src/ros2_medkit_log_bridge/CHANGELOG.rst @@ -2,6 +2,13 @@ Changelog for package ros2_medkit_log_bridge ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Two parameters were being swallowed. ``max_tracked_nodes`` was clamped into range with nothing written to the log, so a configuration file and the running process could disagree in silence. ``report_cooldown_sec`` was worse: the range check was written so that NaN passed it, then passed the "non-positive disables the cooldown" test, and reached ``Duration::from_seconds``, where the conversion is undefined - after which the cooldown window suppressed either every ERROR report or none, with no way to tell which from outside. Both are now validated with a positive, finite range test, and both configuration rows describe what the code does (`#607 `_) +* Integer parameters are read as the int64 a ROS parameter actually holds and clamped in that domain before narrowing, so an out-of-range ``severity_floor`` or ``max_tracked_nodes`` can no longer wrap back into the legal band and pass its own range check (`#607 `_) +* Build and test only: the package is instrumented for coverage (`#582 `_), every integration launch test runs on its own DDS domain taken at run time (`#551 `_, `#597 `_), and the log-bridge integration test waits for ``/rosout`` discovery before emitting its first line instead of racing it +* Contributors: @bburda + 0.6.0 (2026-06-22) ------------------ * Initial release: promote ``/rosout`` log entries (WARN/ERROR/FATAL) to diff --git a/src/ros2_medkit_log_bridge/package.xml b/src/ros2_medkit_log_bridge/package.xml index e69b2b02b..7ec5bee18 100644 --- a/src/ros2_medkit_log_bridge/package.xml +++ b/src/ros2_medkit_log_bridge/package.xml @@ -2,7 +2,7 @@ ros2_medkit_log_bridge - 0.6.0 + 0.7.0 Bridge node promoting ROS2 /rosout log entries to FaultManager faults mfaferek93 diff --git a/src/ros2_medkit_msgs/CHANGELOG.rst b/src/ros2_medkit_msgs/CHANGELOG.rst index 1d45c10b7..faf50eb58 100644 --- a/src/ros2_medkit_msgs/CHANGELOG.rst +++ b/src/ros2_medkit_msgs/CHANGELOG.rst @@ -2,9 +2,13 @@ Changelog for package ros2_medkit_msgs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ -* ``GetRosbag.srv`` gains a ``recording_id`` request field (tried before ``fault_code`` and falling back to it when it names no recording, so a caller holding one identifier that may be either can set both; ``fault_code`` keeps its meaning of "the newest recording of this fault") and ``recording_id`` / ``fault_codes[]`` response fields. ``ListRosbags.srv`` gains a parallel ``recording_ids[]``. ``Snapshot.msg``'s ``bulk_data_id`` now carries a recording id for rosbag snapshots rather than the fault code; the field type is unchanged. Additive, but the service type hashes change, so the gateway and the fault manager must be deployed together (`#620 `_) +0.7.0 (2026-08-27) +------------------ +* ``GetRosbag.srv`` gains a ``recording_id`` request field (tried before ``fault_code`` and falling back to it when it names no recording, so a caller holding one identifier that may be either can set both; ``fault_code`` keeps its meaning of "the newest recording of this fault") and ``recording_id`` / ``fault_codes[]`` response fields. ``ListRosbags.srv`` gains a parallel ``recording_ids[]``. ``Snapshot.msg``'s ``bulk_data_id`` now carries a recording id for rosbag snapshots rather than the fault code; the field type is unchanged. Additive, but the service type hashes change, so the gateway and the fault manager must be deployed together (`#623 `_, `#620 `_) +* ``last_passed`` is exposed on the fault wire, so a consumer can tell when the monitored condition was last observed healthy without inferring it from the status (`#573 `_) +* Rosbag recordings are addressed by recording id rather than by fault code, so a fault holding several recordings can expose each one (`#623 `_) +* The fault contract documentation now matches the implementation on three points that had drifted: ``occurrence_count`` is an edge counter that advances on a new fault and on every re-raise after CLEARED, ``first_occurred`` marks the start of the current occurrence rather than the first one ever, and healed clears and SSE loss accounting are described as the code implements them (`#618 `_) +* Contributors: @bburda, @mfaferek93 0.6.0 (2026-06-22) ------------------ diff --git a/src/ros2_medkit_msgs/package.xml b/src/ros2_medkit_msgs/package.xml index d07d8b33d..29c6e051a 100644 --- a/src/ros2_medkit_msgs/package.xml +++ b/src/ros2_medkit_msgs/package.xml @@ -2,7 +2,7 @@ ros2_medkit_msgs - 0.6.0 + 0.7.0 ROS 2 message and service definitions for ros2_medkit fault management bburda diff --git a/src/ros2_medkit_plugins/ros2_medkit_graph_provider/CHANGELOG.rst b/src/ros2_medkit_plugins/ros2_medkit_graph_provider/CHANGELOG.rst index 7590d7bb1..9b5150d6f 100644 --- a/src/ros2_medkit_plugins/ros2_medkit_graph_provider/CHANGELOG.rst +++ b/src/ros2_medkit_plugins/ros2_medkit_graph_provider/CHANGELOG.rst @@ -2,10 +2,14 @@ Changelog for package ros2_medkit_graph_provider ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ -* ``x-medkit-graph`` pipeline health rework: ``pipeline_status`` no longer misreports ``"broken"`` for edges with no ``/diagnostics`` coverage at all (the previous status model conflated "never observed" with "actively broken"). ``error_reason`` is now freshness-based and its only reachable value is ``metrics_stale``; ``node_offline``, ``topic_stale``, and ``no_data_source`` are gone. ``metrics.source`` is now the actual resolved ``/diagnostics`` publisher node name (resolved per message via publisher GID matching against ``/diagnostics``), omitted rather than hardcoded to ``"greenwave_monitor"`` when it cannot be resolved. Per-function threshold overrides (``plugins.graph_provider.function_overrides..*``) now take effect. ``schema_version`` bumped to ``"2.0.0"`` (`#545 `_) -* Robustness hardening for ``x-medkit-graph`` health signals: freshness and staleness are now computed against a monotonic clock, so a backward wall-clock step (e.g. an NTP correction) can no longer make a dead topic read as fresh; a new ``stale_grace_sec`` setting (default ``2.0``, per-function overridable) debounces a single late ``/diagnostics`` sample so ``pipeline_status`` does not flap to ``"broken"`` and back; a new ``multi_publisher_rate`` setting (default ``"annotate"``, per-function overridable) defends against a duplicate or leftover publisher inflating a topic's summed arrival rate and masking a slow pipeline as healthy, backed by new per-edge ``metrics.publisher_count`` and ``metrics.rate_ambiguous`` fields; ``metrics.source`` is now cleared - not retained - whenever the most recent sample's publisher cannot be resolved, matching its documented latest-wins contract; and invalid ``plugins.graph_provider.*`` configuration, including per-function override field-name typos and wrong-typed values, now logs a warning and falls back to the previous valid value instead of failing silently (`#545 `_) +0.7.0 (2026-08-27) +------------------ +* **Breaking:** the ``x-medkit-graph`` health model is reworked and ``schema_version`` is now ``"2.0.0"``. ``pipeline_status`` is derived from data freshness and node reachability instead of the previous status model, and no longer reports ``"broken"`` for an edge that simply has no ``/diagnostics`` coverage. The ``error_reason`` values ``node_offline``, ``topic_stale`` and ``no_data_source`` are gone; ``metrics_stale`` is the only reachable value. A consumer that matched on the old values needs a change (`#546 `_, `#545 `_) +* ``metrics.source`` reports the resolved publisher rather than a placeholder, falling back to the single publisher of a topic where the RMW does not expose the identity directly, and the negotiation topic filter is anchored to path segments so an unrelated topic whose name merely contains the segment is no longer matched (`#546 `_) +* Per-function configuration overrides take effect, an invalid override warns instead of being applied silently, and the freshness clock is monotonic with a stateless stale grace, so a wall-clock step no longer moves an edge in or out of staleness (`#546 `_) +* Rate measurement is defended against multi-publisher inflation, state is bounded, and shutdown is guarded against callbacks firing on a partially destroyed provider (`#546 `_) +* The plugin and its robustness fields are documented, and the never-executed stale-topic path is removed rather than left as dead code (`#546 `_) +* Contributors: @bburda 0.6.0 (2026-06-22) ------------------ diff --git a/src/ros2_medkit_plugins/ros2_medkit_graph_provider/package.xml b/src/ros2_medkit_plugins/ros2_medkit_graph_provider/package.xml index bcef00fe4..2e4fc2e7a 100644 --- a/src/ros2_medkit_plugins/ros2_medkit_graph_provider/package.xml +++ b/src/ros2_medkit_plugins/ros2_medkit_graph_provider/package.xml @@ -2,7 +2,7 @@ ros2_medkit_graph_provider - 0.6.0 + 0.7.0 Graph provider plugin for ros2_medkit gateway bburda Apache-2.0 diff --git a/src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/CHANGELOG.rst b/src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/CHANGELOG.rst new file mode 100644 index 000000000..09de32ad3 --- /dev/null +++ b/src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/CHANGELOG.rst @@ -0,0 +1,15 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ros2_medkit_graph_watchdog +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.7.0 (2026-08-27) +------------------ +* Initial release of the package: a gateway plugin that raises faults for silent failures in the ROS 2 graph - problems that leave every node alive and every topic present, so nothing in the stack reports them today. The package carries the plugin skeleton, a central reliability gate that holds raises until the graph has quiesced, and a detector registry. Detectors are configured under ``plugins.graph_watchdog.``, each with a ``raise`` / ``advisory`` / ``off`` mode, and an unknown key under ``detectors.`` is reported rather than silently ignored, so a typo cannot quietly disable a check (`#571 `_) +* ``qos_mismatch`` detector: raises ``GRAPH_QOS_MISMATCH`` for a publisher and subscriber whose QoS profiles cannot match, which leaves the connection silently unestablished (`#571 `_) +* ``orphan`` detector: raises ``GRAPH_ORPHAN`` for a one-sided topic - a publisher with no subscriber, or the reverse - but only when a complementary near-miss counterpart exists: same message type, a namespace and leaf within the configured edit distance, and the opposite side. A lone unsubscribed topic raises nothing. The pair is the signature of a remap or a topic-name typo, which is what the detector is for (`#578 `_) +* ``param_drift`` detector: raises ``GRAPH_PARAM_DRIFT`` when a node's live parameter value diverges from the declared expectation (`#580 `_) +* ``lifecycle_expectation`` detector: raises ``GRAPH_NODE_INACTIVE`` for a node named in ``require_active`` that is not in the ``active`` lifecycle state once its ``grace`` window has passed. It raises two further codes for the cases where the state could not be established at all: ``GRAPH_NODE_UNREADABLE`` when the lifecycle service does not answer, and ``GRAPH_NODE_NOT_MANAGED`` when the named node has no lifecycle to read. These are distinct codes on the wire, so a consumer filtering on ``GRAPH_NODE_INACTIVE`` alone sees neither unmeasured case. A node is matched by its ``App::id``, its full FQN, or the bare leaf of that FQN, and the grace streak is counted per node, so naming one node in two documented forms cannot halve the grace that was configured (`#587 `_) +* ``node_death`` detector: raises ``GRAPH_NODE_DISAPPEARED`` for a node that leaves the graph when nothing else is left to report it, with a suppression framework that decides ownership of a departure from knowledge rather than from silence. A node whose lifecycle state was never readable is admitted only provisionally, and is handed back the moment a label arrives saying the departure belonged to the lifecycle detector instead. Zero-config: there is no list of nodes to maintain (`#625 `_, `#624 `_) +* Two silent-fault classes are not delivered in this release. ``GRAPH_TF_STALE`` and ``GRAPH_LATENCY_BUDGET`` have their fault codes reserved in the frozen ``GRAPH_*`` namespace and land in later changes, each against its own issue +* The plugin keeps its ROS entities off the gateway executor, so an entity created and destroyed while the gateway runs cannot have its destructor run on an executor thread concurrently with a create on the same node +* Contributors: @bburda diff --git a/src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/package.xml b/src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/package.xml index 83007db7c..0a01eff81 100644 --- a/src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/package.xml +++ b/src/ros2_medkit_plugins/ros2_medkit_graph_watchdog/package.xml @@ -2,8 +2,8 @@ ros2_medkit_graph_watchdog - 0.6.0 - Gateway plugin that detects silent faults in the ROS 2 graph: QoS mismatches, topic-name typos, dead nodes, stale transforms, parameter drift, lifecycle expectations and pipeline timing budgets. + 0.7.0 + Gateway plugin that detects silent faults in the ROS 2 graph: QoS mismatches, orphaned topics left by a name typo, dead nodes, parameter drift and unmet lifecycle expectations. bburda Apache-2.0 diff --git a/src/ros2_medkit_plugins/ros2_medkit_opcua/CHANGELOG.rst b/src/ros2_medkit_plugins/ros2_medkit_opcua/CHANGELOG.rst index b2b1b2009..9f37ccc7f 100644 --- a/src/ros2_medkit_plugins/ros2_medkit_opcua/CHANGELOG.rst +++ b/src/ros2_medkit_plugins/ros2_medkit_opcua/CHANGELOG.rst @@ -2,6 +2,18 @@ Changelog for package ros2_medkit_opcua ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Config-less discovery. A read-only network scan finds the OPC UA server instead of requiring its endpoint up front (`#509 `_), ``auto_browse`` walks the address space recursively and builds the SOVD tree from it (`#510 `_), and identity, writability and fault triggers are read from the device itself rather than declared in a node map (`#544 `_) +* Native OPC UA Alarms and Conditions become medkit faults with no configuration (`#511 `_). Alarms are routed to distinct faults by message substring (`#506 `_), ``severity`` is accepted as an alias in ``event_alarms`` (`#501 `_), and events that are not conditions are dropped from the alarm path instead of being reported as alarms (`#552 `_) +* Asset identity is populated from the device nameplate, including ``order_code`` (`#492 `_, `#499 `_) +* ``PLC_COMMS_LOST`` is raised when the connection to the PLC drops, so a lost link is a reported fault rather than an absence of data (`#507 `_) +* Security and reconnect hardening: a secure connection profile, replay of alarm state after a reconnect, and correct handling of several simultaneous alarms (`#485 `_) +* Fixed a data race between the REST thread and the poll thread on the pending-report buffer that crashed the gateway with a SIGSEGV (`#519 `_, `#521 `_) +* Fault detection moved onto the shared ``ros2_medkit_fault_detection`` evaluator, and the node map enforces global fault-code uniqueness so two rules cannot claim the same code (`#486 `_) +* This package is still excluded from the rosdistro binary release pending vendoring of open62541pp (`#366 `_) +* Contributors: @bburda, @mfaferek93 + 0.6.0 (2026-06-22) ------------------ * No functional changes; version bump for the coordinated 0.6.0 release. diff --git a/src/ros2_medkit_plugins/ros2_medkit_opcua/package.xml b/src/ros2_medkit_plugins/ros2_medkit_opcua/package.xml index 8db482717..9087dd2f9 100644 --- a/src/ros2_medkit_plugins/ros2_medkit_opcua/package.xml +++ b/src/ros2_medkit_plugins/ros2_medkit_opcua/package.xml @@ -2,7 +2,7 @@ ros2_medkit_opcua - 0.6.0 + 0.7.0 OPC-UA gateway plugin for ros2_medkit - bridges PLC systems into the SOVD entity tree mfaferek93 Apache-2.0 diff --git a/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/CHANGELOG.rst b/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/CHANGELOG.rst index 64744bf16..1bdcf0f2b 100644 --- a/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/CHANGELOG.rst +++ b/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/CHANGELOG.rst @@ -2,6 +2,13 @@ Changelog for package ros2_medkit_sovd_service_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* A cyclic-subscription request naming a resource path the collection cannot stream is refused instead of being accepted and never delivering +* The sampler declaration stays inside the gateway, so the service interface no longer carries a second copy of it +* Build and test only: the package is instrumented for coverage (`#582 `_), and its tests take a DDS domain at run time from the shared allocator (`#597 `_) +* Contributors: @bburda + 0.6.0 (2026-06-22) ------------------ * ``list_entity_faults`` handles a fault ``status`` returned as an object (not just a string), supporting peer-fault aggregation across daisy-chained gateways (`#419 `_) diff --git a/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/package.xml b/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/package.xml index eff5fa8db..9ec44fdb9 100644 --- a/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/package.xml +++ b/src/ros2_medkit_plugins/ros2_medkit_sovd_service_interface/package.xml @@ -2,7 +2,7 @@ ros2_medkit_sovd_service_interface - 0.6.0 + 0.7.0 SOVD Service Interface plugin - exposes medkit entity tree and fault data via ROS 2 services bburda mfaferek93 diff --git a/src/ros2_medkit_serialization/CHANGELOG.rst b/src/ros2_medkit_serialization/CHANGELOG.rst index 29c9c0524..a0de946a0 100644 --- a/src/ros2_medkit_serialization/CHANGELOG.rst +++ b/src/ros2_medkit_serialization/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package ros2_medkit_serialization ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.7.0 (2026-08-27) +------------------ +* Build and test only: the package is instrumented for coverage (`#582 `_), its ROS-dependent tests take a DDS domain at run time from the shared allocator while the pure-logic ones are opted out (`#597 `_), and clang-tidy no longer reports findings in vendored headers that the header filter matched +* Contributors: @bburda, @mfaferek93 + 0.6.0 (2026-06-22) ------------------ * ``TypeIntrospection`` resolves service and action ``type_info`` schemas lazily and caches them as shared, immutable per-type objects, so discovery no longer rebuilds and deep-copies operation schemas on every refresh (`#462 `_) diff --git a/src/ros2_medkit_serialization/package.xml b/src/ros2_medkit_serialization/package.xml index 3e056f952..e36557226 100644 --- a/src/ros2_medkit_serialization/package.xml +++ b/src/ros2_medkit_serialization/package.xml @@ -2,7 +2,7 @@ ros2_medkit_serialization - 0.6.0 + 0.7.0 Runtime JSON to ROS 2 message serialization library bburda Apache-2.0