Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file
Package Dependencies
System Dependencies
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged ros2_medkit_graph_watchdog at Robotics Stack Exchange
Package Summary
| Version | 0.6.0 |
| License | Apache-2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/selfpatch/ros2_medkit.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-30 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- bburda
Authors
ros2_medkit_graph_watchdog
Gateway plugin that detects silent faults in the ROS 2 graph: failures where every node is up, nothing logs an error, and the robot is still broken.
Detectors read the graph and raise faults through a ReportFault service client on the
gateway node; the faults surface via FaultManager on the gateway /faults API.
This package carries the plugin skeleton, the central reliability gate that holds raises
until the graph has quiesced, and two detectors, qos_mismatch and orphan. The remaining
silent-fault classes land in follow-up changes, each against its own issue; their fault
codes are already reserved in the frozen GRAPH_* namespace (see “Fault codes”).
Build
With the gateway built and sourced:
colcon build --packages-select ros2_medkit_graph_watchdog
colcon test --packages-select ros2_medkit_graph_watchdog && colcon test-result --verbose
Load into the gateway
ros2 run ros2_medkit_gateway gateway_node --ros-args \
-p plugins:="[graph_watchdog]" \
-p "plugins.graph_watchdog.path:=$(ros2 pkg prefix ros2_medkit_graph_watchdog)/lib/ros2_medkit_graph_watchdog/libros2_medkit_graph_watchdog.so"
Configuration (plugins.graph_watchdog.<key>)
| Key | Type | Default | Meaning |
|---|---|---|---|
tick_interval_ms |
int | 1000 |
Detector tick cadence. |
warmup_cycles |
int | 5 |
An entity must be continuously present for this many ticks before it arms. A mid-run restart re-warms it. Enforced centrally, see “Reliability (bringup-quiesce)” below. |
prune_grace |
int | 60 |
Default injected into every detector’s own config before configure(); a per-detector detectors.<id>.prune_grace overrides it. Used by detectors that keep per-key bookkeeping. |
detectors.<id>.mode |
string | bool | raise |
raise = push faults; advisory = observe, do not push; off = disabled. Bare off/no (YAML booleans) also disable; bare on/yes mean raise. |
detectors.<id>.<field> |
any | - | Per-detector thresholds, passed to that detector’s configure(). |
Nested delivery. Keys are written dotted in YAML or --ros-args, and the gateway
delivers them to the plugin as a nested object. A bare off is a YAML 1.1 boolean, so
mode: off arrives as false, not the string "off"; both forms disable the detector.
Unknown keys are reported. Anything under detectors.<id> that the detector does not
read produces one startup warning naming the key and listing the ones that exist, so a
typo like allow_list: for allowlist: cannot silently do nothing. A typo in the detector
id itself is warned about the same way, with the registered ids listed.
orphan keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 10 |
Consecutive sweeps a candidate pair must hold before it is reported. A staged bringup or a restart makes one side missing for a short time, which looks exactly like a typo until the other side appears. |
max_edit_distance |
int | 1 |
How many character edits the LEAF (the part after the last /) may differ by and still count as the same intended name. Values outside 1..8 keep the default: past a few edits a “near miss” starts matching unrelated topics. |
namespace_edit_distance |
int | 0 |
The same budget for the namespace, spent separately. 0 means the namespace must match to the character, which is what makes /robot1/scan and /robot2/scan two robots rather than a typo. Raise it to catch a misspelled namespace, and read the warning under the table first. Values outside 0..8 keep the default. |
allowlist |
string[] | [] |
Topic names never reported. Exact match only, never a prefix, so allowlisting /r1/scan does not also silence /r2/scan. |
What this detector will not catch, on purpose. A pair whose names match once every run of
digits is collapsed is treated as an enumeration of sensors, not a typo: /lidar_1 next to
/lidar_2, each one-sided, is an ordinary state on a multi-sensor robot and reporting it would
be a false alarm on every such machine. Collapsing runs rather than comparing character
positions is what also covers /lidar_9 next to /lidar_10. The cost is that a typo which
is a digit stops being reported. An appended digit is not that case: /scan1 collapses to
/scan#, which is not /scan, so it is still reported.
A misspelled namespace is opt-in, and it can be expensive. /robott/scan against
/robot/scan is one edit, but in the namespace, so by default it is not reported. Setting
namespace_edit_distance: 1 reports it. Check the naming scheme before doing that: on a fleet
named with letters, /amr_a and /amr_b are also one edit apart, and every robot in the
fleet will be reported as a typo. A numbered fleet is safe, since /robot1 against /robot2
differs only in its numeric field and the rule above already spares it.
What it cannot catch at all. A namespace added or dropped by mistake, /scan against
/robot/scan, is six edits apart, so no budget that is still specific will reach it. Do not
rely on this detector for that class.
qos_mismatch keys
| Key | Type | Default | Meaning |
|---|---|---|---|
mode |
string | raise |
As above. |
grace |
int | 3 |
Consecutive ticks a topic must stay affected before it is reported. Endpoint discovery is not atomic, so a subscriber can be visible before a publisher’s QoS is, which reads as starvation for a tick or two. |
allowlist |
string[] | [] |
Subscriber FQNs never reported. Exact match only. |
Where GRAPH_* faults hang
Every GRAPH_* fault is raised with source_id: graph_watchdog. That is an App the
plugin publishes itself, from its own IntrospectionProvider, marked external: true.
So:
-
GET /api/v1/apps/graph_watchdog/faultslists them. -
GET /api/v1/components/<host>/faultslists them too, because the App is attached to the host Component and a Component resolves its fault scope through its child apps.
It has to be an entity the plugin owns. Scoping these faults to the host Component
directly - the obvious choice - makes them reachable from no endpoint at all:
collect_component_app_fqns only puts a Component’s own id in the scope set when
external is true, and a runtime host Component built by HostInfoProvider never sets
it. There is no server-level /faults/{code} route either, so the flat /faults list
was the only place these faults existed.
File truncated at 100 lines see the full file