fix(gateway): surface external plugin-entity faults (list, detail, freeze-frame) via fault-scope ownership#503
fix(gateway): surface external plugin-entity faults (list, detail, freeze-frame) via fault-scope ownership#503mfaferek93 wants to merge 2 commits into
Conversation
…eeze-frame) An external protocol-plugin app (e.g. a PLC over OPC UA) has no ROS FQN, so fault-scope resolution produced an empty ownership set and the per-entity fault routes dropped every fault it reported. An external app now owns faults reported under its own entity id; ROS-bound entities keep their FQN scope. The per-entity detail returns the fault_manager freeze-frame record, and the opcua plugin now consumes list_entity_faults per its documented bare-array contract.
There was a problem hiding this comment.
Pull request overview
This PR fixes per-entity SOVD fault list/detail behavior for external protocol-plugin entities (no ROS binding / empty FQN) by ensuring fault-scope ownership includes the entity’s bare ID in the external-app case, and by ensuring plugin-entity fault detail prefers the fault_manager’s environment-enriched record (freeze-frame) when the entity owns the fault, with a fallback to the plugin’s provider when the fault_manager doesn’t have it. This addresses #502’s root cause and acceptance criteria around list, detail, freeze-frame, and unchanged cross-entity guard behavior.
Changes:
- Extend fault-scope resolution so external Apps with empty
effective_fqn()contribute their own entity ID as the owned scope token (non-external unbound apps remain skipped). - Update fault detail handling for plugin-owned entities to prefer fault_manager’s
get_fault_with_envpayload when the fault is in the entity’s resolved scope, otherwise fall back to the plugin provider. - Align OPC UA plugin + test fakes with the
PluginContext::list_entity_faultscontract (bare array), and add regression tests for ownership and freeze-frame propagation.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_opcua_plugin.cpp | Adjust test fake to return bare fault array; add regression test ensuring get_fault locates items in that array. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_opcua_identity.cpp | Align identity test fake with bare-array list_entity_faults contract. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/src/opcua_plugin.cpp | Consume list_entity_faults as a bare array for list/detail fault lookups. |
| src/ros2_medkit_gateway/test/test_plugin_entity_routing.cpp | Add test pinning plugin-fallback payload behavior when fault_manager does not hold the fault. |
| src/ros2_medkit_gateway/test/test_handler_context.cpp | Add tests proving external-app bare-ID ownership is granted only for external=true, and that bound-app behavior is unchanged. |
| src/ros2_medkit_gateway/test/test_fault_manager_routing.cpp | Add test pinning freeze-frame presence in fault_manager enriched detail payload for the external-entity case. |
| src/ros2_medkit_gateway/test/test_fault_handlers.cpp | Add tests for bare-ID scope matching/guard behavior and freeze-frame shaping in SOVD fault detail responses. |
| src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp | Prefer fault_manager enriched detail for plugin entities when the fault is in-scope; otherwise fall back to plugin provider. |
| src/ros2_medkit_gateway/src/core/faults/fault_scope.cpp | Extend scope resolution: external app with empty FQN owns faults under its bare entity ID; non-external unbound app remains skipped. |
bburda
left a comment
There was a problem hiding this comment.
Additional notes outside the inline comment:
- The get_fault enriched-vs-fallback branch (the main change) has no end-to-end handler test. The four pieces are unit-tested, but nothing builds a node and calls
get_fault, so the security selection is unpinned: an entity that does NOT own a fault code must fall through to the plugin provider (404), not return another entity's enriched freeze-frame record. If the new scope check is later changed, a cross-entity read leak would not be caught. - The per-entity list and single-fault clear routes for an external entity are only covered at the resolver unit level, not end to end. The empty-list bug this PR fixes has no regression test on its own route.
| // resolution grants it a scope set of exactly {its bare id}. These pins prove | ||
| // the guard treats a bare id like any other scope token: it owns faults | ||
| // reported under that id, and still blocks cross-entity disclosure (GET) and | ||
| // cross-entity clear (DELETE), which both flow through this predicate. |
There was a problem hiding this comment.
This says cross-entity clear (DELETE) flows through this predicate, but for a plugin or external entity it does not. The single-fault DELETE takes the is_plugin branch and delegates to the plugin clear_fault, which for OPC UA forwards the code to /fault_manager/clear_fault with no scope check. So DELETE /apps/A/faults/{code owned by B} clears B's fault. Either apply the same fault_in_source_scope guard on the plugin DELETE branch, or correct this comment so it does not claim a property the plugin path does not enforce.
The plugin-owned entity path in get_fault/clear_fault forwarded fault_code to get_fault_with_env and the plugin FaultProvider without the empty/length guard the non-plugin path applied. Hoist the check ahead of plugin delegation so both paths reject empty or oversized codes consistently.
External protocol-plugin entities (e.g. a PLC over OPC UA) report faults to the fault_manager under their entity id, but the per-entity SOVD fault routes dropped them: an external app has no ROS FQN, so fault-scope resolution produced an empty ownership set and the scope filter rejected every fault the entity reported.
Closes #502.