Problem
Plugins that implement UpdateProvider (or any other plugin that mutates the entity surface at runtime) currently have no way to tell the gateway "my entities changed, please refresh your tree". The gateway's entity tree is populated from:
- The manifest file, loaded once at startup.
- Runtime discovery, which picks up ROS 2 graph changes on a polling interval.
When a plugin deploys a new app (e.g. drops a Python node into /opt/app-root/addons/<id>/main.py and starts it) the runtime discovery catches the new node within a few seconds, but:
- It shows up as an orphan (not in manifest), so it can't be attached to a parent component.
- There's no way for the plugin to ship a matching manifest fragment at deploy time and have the gateway pick it up.
- After the plugin deletes the app (e.g. rollback), the manifest entry — if it ever existed — is never cleared.
This is a gap for any plugin whose job is to mutate the entity surface: OTA installers, rosbag injectors, dynamic config deployers, hot-reloadable adapters.
Proposal
Two parts:
1. PluginContext::notify_entities_changed(scope)
Add to ros2_medkit_gateway::PluginContext a method plugins call when they have finished mutating entities:
struct EntityChangeScope {
std::optional<std::string> area_id;
std::optional<std::string> component_id;
// empty/null = full refresh
};
virtual void notify_entities_changed(const EntityChangeScope & scope) = 0;
The gateway responds by:
- Re-reading the manifest file from disk (cheap, just a single yaml parse).
- Scanning a well-known fragments directory (new, see part 2).
- Re-running runtime discovery for the affected scope.
- Merging the fresh result into the entity cache via the existing
merge_pipeline.
Existing ResourceChangeNotifier stays as-is — it's for resource-item changes (faults, data, config), not entity-surface changes. The two concerns are separate.
2. Manifest fragments directory
Introduce a gateway parameter manifest.fragments_dir (default: empty = disabled). When set, the manifest loader scans that directory for *.yaml / *.yml files at every manifest load and merges them on top of the base manifest. Fragment schema is a strict subset of the main manifest (apps / functions / subcomponents only; no new areas or top-level components without allow_manifest_override: true).
This gives plugins a place to drop per-deploy manifest chunks alongside their deployed files. Removing the fragment (during plugin's cleanup / rollback) + calling notify_entities_changed makes the entry disappear.
Non-goals
- No hot-reload of plugin binaries.
- No change to SSE / trigger / websocket APIs (use existing
ResourceChangeNotifier for resource-level events).
- No new HTTP endpoint (the gateway's admin surface is and should remain SOVD-only).
Compatibility
PluginContext is a versioned interface (PLUGIN_API_VERSION). This change bumps the API version and gets a default no-op implementation for backwards compatibility with plugins built against the previous header.
manifest.fragments_dir defaults to empty, so no behavior change for users who don't opt in.
Acceptance criteria
Problem
Plugins that implement
UpdateProvider(or any other plugin that mutates the entity surface at runtime) currently have no way to tell the gateway "my entities changed, please refresh your tree". The gateway's entity tree is populated from:When a plugin deploys a new app (e.g. drops a Python node into
/opt/app-root/addons/<id>/main.pyand starts it) the runtime discovery catches the new node within a few seconds, but:This is a gap for any plugin whose job is to mutate the entity surface: OTA installers, rosbag injectors, dynamic config deployers, hot-reloadable adapters.
Proposal
Two parts:
1.
PluginContext::notify_entities_changed(scope)Add to
ros2_medkit_gateway::PluginContexta method plugins call when they have finished mutating entities:The gateway responds by:
merge_pipeline.Existing
ResourceChangeNotifierstays as-is — it's for resource-item changes (faults, data, config), not entity-surface changes. The two concerns are separate.2. Manifest fragments directory
Introduce a gateway parameter
manifest.fragments_dir(default: empty = disabled). When set, the manifest loader scans that directory for*.yaml/*.ymlfiles at every manifest load and merges them on top of the base manifest. Fragment schema is a strict subset of the main manifest (apps / functions / subcomponents only; no new areas or top-level components withoutallow_manifest_override: true).This gives plugins a place to drop per-deploy manifest chunks alongside their deployed files. Removing the fragment (during plugin's cleanup / rollback) + calling
notify_entities_changedmakes the entry disappear.Non-goals
ResourceChangeNotifierfor resource-level events).Compatibility
PluginContextis a versioned interface (PLUGIN_API_VERSION). This change bumps the API version and gets a default no-op implementation for backwards compatibility with plugins built against the previous header.manifest.fragments_dirdefaults to empty, so no behavior change for users who don't opt in.Acceptance criteria
EntityChangeScopetype andPluginContext::notify_entities_changedmethod.manifest.fragments_dirparameter with fragment loader.notify_entities_changed, the new app appears inGET /apps; removes the fragment + notifies, the app disappears.docs/design/page describing the lifecycle, extended CHANGELOG, PLUGIN_API_VERSION bump.