Class PluginContext

Class Documentation

class PluginContext

Context interface providing plugins access to gateway data and utilities.

Passed to plugins during lifecycle via set_context(). Replaces the old set_node() by providing both ROS 2 node access and gateway-level abstractions.

Thread Safety

All methods are safe to call from any thread. Entity and fault queries use the gateway’s thread-safe caches internally.

Note

This interface is versioned alongside PLUGIN_API_VERSION. New methods may be added in future versions (entity data access, configuration queries, etc.).

Public Functions

virtual ~PluginContext() = default
virtual rclcpp::Node *node() const = 0

Get ROS 2 node pointer for subscriptions, service clients, etc.

virtual std::optional<PluginEntityInfo> get_entity(const std::string &id) const = 0

Look up an entity by ID. Returns nullopt if not found.

virtual std::vector<PluginEntityInfo> get_child_apps(const std::string &component_id) const = 0

Get all Apps belonging to a Component (for aggregation endpoints)

virtual nlohmann::json list_entity_faults(const std::string &entity_id) const = 0

List faults for a given entity. Returns JSON array of fault objects. Empty array if entity has no faults or fault manager is unavailable.

virtual std::optional<PluginEntityInfo> validate_entity_for_route(const PluginRequest &req, PluginResponse &res, const std::string &entity_id) const = 0

Validate entity exists and matches route type, sending SOVD error if not.

Use this in get_routes() handlers to validate entity IDs from path params. On failure, an appropriate SOVD GenericError response is sent automatically.

Parameters:
  • req – Plugin request (extracts expected entity type from path)

  • res – Plugin response (error sent here on failure)

  • entity_id – Entity ID from path parameter (e.g., req.path_param(1))

Returns:

Entity info if valid, nullopt if error was sent

virtual void register_capability(SovdEntityType entity_type, const std::string &capability_name) = 0

Register a custom capability for all entities of a given type.

The capability will appear in the entity’s capabilities array with an auto-generated href. For example, registering “x-medkit-traces” for SovdEntityType::APP produces: {“name”: “x-medkit-traces”, “href”: “/api/v1/apps/{id}/x-medkit-traces”}

The plugin must also return a matching route from get_routes().

Parameters:
  • entity_type – Entity type to add the capability to

  • capability_name – Capability name (use x- prefix for vendor extensions)

virtual void register_entity_capability(const std::string &entity_id, const std::string &capability_name) = 0

Register a custom capability for a specific entity.

Like register_capability(entity_type, name) but scoped to a single entity.

Parameters:
  • entity_id – Specific entity ID

  • capability_name – Capability name (use x- prefix for vendor extensions)

virtual std::vector<std::string> get_type_capabilities(SovdEntityType entity_type) const = 0

Get plugin-registered capabilities for an entity type.

virtual std::vector<std::string> get_entity_capabilities(const std::string &entity_id) const = 0

Get plugin-registered capabilities for a specific entity.

virtual LockAccessResult check_lock(const std::string &entity_id, const std::string &client_id, const std::string &collection) const = 0

Check if a lock blocks access to a collection on an entity.

Parameters:
  • entity_id – Entity to check

  • client_id – Client requesting access

  • collection – Resource collection being accessed (e.g. “configurations”)

Returns:

LockAccessResult with allowed/denied status and details

virtual tl::expected<LockInfo, LockError> acquire_lock(const std::string &entity_id, const std::string &client_id, const std::vector<std::string> &scopes, int expiration_seconds) = 0

Acquire a lock on an entity.

Parameters:
  • entity_id – Entity to lock

  • client_id – Client acquiring the lock

  • scopes – Optional lock scopes (empty = all collections)

  • expiration_seconds – Lock TTL in seconds

Returns:

LockInfo on success, LockError on failure

virtual tl::expected<void, LockError> release_lock(const std::string &entity_id, const std::string &client_id) = 0

Release a lock on an entity.

Parameters:
  • entity_id – Entity to unlock

  • client_id – Client releasing the lock

Returns:

void on success, LockError on failure

inline virtual IntrospectionInput get_entity_snapshot() const

Get a snapshot of all discovered entities (areas, components, apps, functions). Returns an IntrospectionInput populated from the current entity cache.

inline virtual nlohmann::json list_all_faults() const

List all faults across all entities. Returns JSON with “faults” array. Empty object if fault manager is unavailable.

inline virtual void register_sampler(const std::string&, const std::function<tl::expected<nlohmann::json, std::string>(const std::string&, const std::string&)>&)

Register a cyclic subscription sampler for a custom collection.

virtual ResourceChangeNotifier *get_resource_change_notifier() = 0

Get the ResourceChangeNotifier for publishing or subscribing to resource changes.

Always returns a valid pointer - ResourceChangeNotifier is created unconditionally in GatewayNode regardless of trigger configuration.

virtual ConditionRegistry *get_condition_registry() = 0

Get the ConditionRegistry for registering custom trigger condition evaluators.

Always returns a valid pointer - ConditionRegistry is created unconditionally in GatewayNode regardless of trigger configuration.

inline virtual void notify_entities_changed(const EntityChangeScope&)

Tell the gateway that this plugin has finished mutating entities.

Call this AFTER any change that adds, removes, or restructures entities on disk or in the running runtime (deploying a new node under a manifest fragment, removing a previously-deployed app, renaming a component). The gateway responds by re-reading its manifest sources (including any configured fragments directory) and running a discovery pass for the affected scope. By the time this returns, subsequent get_entity and HTTP discovery endpoints reflect the new surface.

Thread safety

Safe to call from any thread. The v7 reference implementation is SYNCHRONOUS: the caller’s thread drives the full manifest reload + discovery refresh before the call returns, under an internal refresh mutex that also serializes the periodic refresh timer. As a result:

  • the call may block for seconds on large manifests / slow plugins;

  • plugins MUST NOT hold their own mutexes across this call if any of their own callbacks (introspect, data/operation/fault providers) could try to reacquire that mutex - doing so deadlocks;

  • calling this method from within your own IntrospectionProvider:: introspect() callback is detected and skipped with a warning log (see GatewayNode::handle_entity_change_notification). The design assumes introspect already runs inside a refresh pass, so a nested notification would be redundant.

Backwards compatibility

Default implementation is a no-op. Plugin source written against plugin API v6 compiles unchanged against v7 headers (source-compatible) - no code changes are required. The plugin loader compares the exported plugin_api_version() against the gateway’s PLUGIN_API_VERSION with strict equality, so a plugin .so pre-compiled against v6 IS rejected; recompilation against v7 headers is required.

Fragment file contract (when used with discovery.manifest.fragments_dir)

Plugins that deploy / remove manifest fragments on disk before calling this method MUST publish the final content atomically. The gateway’s fragment scanner reads each file on the caller’s thread once the notification arrives; a partially-written fragment causes the reload to fail, which rolls back the entire manifest merge (see the design doc’s “all-or-nothing fragment contract” section). The recommended pattern is: write to fragments_dir/.tmp-<id>.yaml, fsync, then rename() (POSIX atomic within the same filesystem) to fragments_dir/<id>.yaml. The rename is the commit point that makes the fragment visible to the next reload.

Parameters:

scope – Hint that lets the gateway limit the rediscovery work. EntityChangeScope::full_refresh() asks for a global pass.