Skip to content

feat(gateway): expose entity context on /faults/stream SSE events#400

Open
bburda wants to merge 1 commit into
mainfrom
issue380-faults-stream-entity-context
Open

feat(gateway): expose entity context on /faults/stream SSE events#400
bburda wants to merge 1 commit into
mainfrom
issue380-faults-stream-entity-context

Conversation

@bburda
Copy link
Copy Markdown
Collaborator

@bburda bburda commented May 14, 2026

Pull Request

Summary

GET /api/v1/faults/stream event payloads now carry an optional x-medkit SOVD payload-extension object with entity_type and entity_id fields when the gateway can resolve the fault's first reporting_sources entry back to a SOVD entity. Consumers can then hit /{entity_type}/{entity_id}/bulk-data/rosbags/{fault_code} directly, replacing the O(N) HEAD-probe workaround that external integrations need today.

Nested under x-medkit per the SOVD payload-extension convention (matches existing x-medkit.aggregation_level, x-medkit.phase, etc.). Flat x-medkit-* names are reserved for endpoint paths (/x-medkit-graph) and error codes, not payload fields.

Resolution mirrors the existing TriggerFaultSubscriber / LogManager pattern:

  • Manifest / hybrid mode: cache's node-to-app linking index (with and without leading /).
  • Runtime-only fallback: ROS FQN's last segment, only emitted when an App with that id actually exists in the cache - we never point consumers at a 404.
  • No match: x-medkit object omitted entirely (backward-compatible for existing SOVD consumers).

Resolution is snapshotted in on_fault_event and stored alongside the buffered event (new QueuedEvent struct), so a discovery refresh between enqueue and stream-out cannot retroactively flip the entity reported to consumers, and the format path stays lock-free wrt the entity cache.

Example payload:

{
  "event_type": "fault_confirmed",
  "fault": {"fault_code": "MOTOR_OVERHEAT", "...": "..."},
  "timestamp": 1735830000.123,
  "x-medkit": {"entity_type": "apps", "entity_id": "motor_controller"}
}

Issue


Type

  • Bug fix
  • New feature or tests
  • Breaking change
  • Documentation only

Testing

  • 4 new unit tests in test_sse_fault_handler.cpp covering: manifest-app match, runtime-fallback match, no-match (entity absent from cache), and empty reporting_sources.
  • Full SSE fault handler suite (15 tests) green.
  • Full gateway unit suite (2213 tests, 0 failures) green when run serially.
  • clang-format / clang-tidy / ament-copyright clean on touched files.

Checklist

  • Breaking changes are clearly described (and announced in docs / changelog if needed)
  • Tests were added or updated if needed
  • Docs were updated if behavior or public API changed

@bburda bburda force-pushed the issue380-faults-stream-entity-context branch 2 times, most recently from 16d686d to e4e5b7b Compare May 14, 2026 11:40
@bburda bburda self-assigned this May 14, 2026
@bburda bburda requested a review from mfaferek93 May 14, 2026 12:10
@bburda bburda marked this pull request as ready for review May 14, 2026 12:10
Copilot AI review requested due to automatic review settings May 14, 2026 12:10
@bburda bburda changed the title feat(gateway,#380): expose entity context on /faults/stream SSE events feat(gateway): expose entity context on /faults/stream SSE events May 14, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an optional x-medkit SOVD payload-extension object (entity_type, entity_id) to events streamed from GET /api/v1/faults/stream, letting consumers jump directly to /{entity_type}/{entity_id}/bulk-data/rosbags/{fault_code} instead of HEAD-probing entities. Resolution is snapshotted at fault arrival time so a discovery refresh cannot retroactively change the reported entity, and the lookup mirrors the node_to_app mapping plus runtime-fallback heuristics already used by gateway_node / ros2_runtime_introspection.

Changes:

  • Introduce QueuedEvent (id + event + resolved EntityContext) replacing the pair<id, event> in the SSE buffer, and resolve entity context before the queue lock in on_fault_event.
  • Implement SSEFaultHandler::resolve_entity_context: manifest/hybrid via cache.resolve_node_to_app (with and without leading slash), runtime fallback to FQN last segment, and collision-disambiguated <ns_prefix>_<name> — only when the App exists in the cache.
  • Document the new extension in the package README, top-level REST docs, and changelog; add 7 unit tests covering snapshot semantics, manifest/runtime/collision/no-leading-slash variants, and the omit cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/handlers/sse_fault_handler.hpp Adds EntityContext/QueuedEvent types, new resolve_entity_context() declaration, and switches the event deque element type.
src/ros2_medkit_gateway/src/http/handlers/sse_fault_handler.cpp Snapshots entity context at enqueue, emits x-medkit in format_sse_event, implements manifest + runtime fallback resolution with has_app validation.
src/ros2_medkit_gateway/test/test_sse_fault_handler.cpp Adds 7 GTests covering match/no-match, runtime fallback, manifest mapping (with/without leading slash), collision-disambiguated app, snapshot-at-enqueue, and empty reporting_sources.
src/ros2_medkit_gateway/README.md Documents the new x-medkit payload extension and updated example payload for the SSE stream endpoint.
src/ros2_medkit_gateway/CHANGELOG.rst Adds the Unreleased feature entry referencing #380.
docs/api/rest.rst Updates the global SOVD extensions section with the new field on /faults/stream.

@bburda bburda force-pushed the issue380-faults-stream-entity-context branch from e4e5b7b to 73e55e4 Compare May 14, 2026 12:23
Each `GET /api/v1/faults/stream` event payload now carries an optional
`x-medkit` SOVD payload-extension object with `entity_type` and
`entity_id` fields when the gateway can resolve the fault's first
reporting source back to a SOVD entity. Consumers can hit
`/{entity_type}/{entity_id}/bulk-data/rosbags/{fault_code}` directly
instead of enumerating apps + components and HEAD-probing each one.

Nested under `x-medkit` per the SOVD payload-extension convention
(matches `x-medkit.aggregation_level`, `x-medkit.phase`, etc.). Flat
`x-medkit-*` names are reserved for endpoint paths (`/x-medkit-graph`)
and error codes, not payload fields.

Resolution chain (mirrors gateway_node's node_resolver lambda for the
manifest path):

- Manifest / hybrid: cache's node-to-app linking index, both with and
  without the leading slash on the FQN.
- Runtime fallback: FQN's last segment, validated against
  `cache.has_app()` so we never point consumers at a 404.
- Runtime collision fallback: `<ns_prefix>_<name>` form (slashes in
  the namespace replaced with `_`), matching the disambiguation rule
  in ros2_runtime_introspection.cpp for nodes that share a name
  across namespaces.
- No match: `x-medkit` object omitted entirely; an `RCLCPP_DEBUG`
  trace records the unresolved source so operators can diagnose
  consumers stuck on the discovery fallback path. Backward-compatible
  for existing SOVD consumers.

Entity resolution is snapshotted in `on_fault_event` (before the
queue lock is acquired) and stored as `std::optional<EntityContext>`
on the buffered event. A discovery refresh between enqueue and
stream-out cannot retroactively flip the entity reported to
consumers, and the format path stays lock-free with respect to the
entity cache.
@bburda bburda force-pushed the issue380-faults-stream-entity-context branch from 73e55e4 to 13373e3 Compare May 14, 2026 21:07
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.

Global /faults/stream SSE events lack entity context for downstream consumers

2 participants