Skip to content

fix(gateway): surface external plugin-entity faults (list, detail, freeze-frame) via fault-scope ownership#503

Open
mfaferek93 wants to merge 2 commits into
mainfrom
feat/external-entity-fault-scope
Open

fix(gateway): surface external plugin-entity faults (list, detail, freeze-frame) via fault-scope ownership#503
mfaferek93 wants to merge 2 commits into
mainfrom
feat/external-entity-fault-scope

Conversation

@mfaferek93

Copy link
Copy Markdown
Collaborator

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.

  • Fault-scope: an external app with no ROS binding now contributes its own entity id to its scope set, so it is recognised as the owner of faults reported under that id. ROS-bound entities are unchanged; the change is confined to the empty-FQN external case in the fault-scope resolution.
  • Per-entity detail: for a plugin entity that owns the fault, the detail route returns the fault_manager's environment-enriched record (including the freeze-frame), consistent with the non-plugin detail path, and falls back to the plugin's own fault provider for faults the fault_manager does not hold.
  • The per-entity list now surfaces the entity's faults via the same fixed ownership.
  • Scope-guard behaviour (cross-entity disclosure on GET, cross-entity clear on DELETE) is unchanged for bound entities and covered by regression tests.

Closes #502.

…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.
Copilot AI review requested due to automatic review settings July 4, 2026 19:28
@mfaferek93 mfaferek93 self-assigned this Jul 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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_env payload 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_faults contract (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.

Comment thread src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp

@bburda bburda left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sovd: per-entity fault list and detail are empty for external protocol-plugin entities

3 participants