Manifest Schema Reference
This document describes the complete YAML schema for SOVD system manifests.
Top-Level Structure
A manifest file has the following top-level structure:
manifest_version: "1.0" # Required - manifest schema version
metadata: # Optional - document metadata
name: string
version: string
description: string
config: # Optional - discovery behavior settings
unmanifested_nodes: string
inherit_runtime_resources: boolean
allow_manifest_override: boolean
areas: [] # Optional - area definitions
components: [] # Optional - component definitions
assets: [] # Optional - manual asset inventory entries
apps: [] # Optional - app definitions
functions: [] # Optional - function definitions
scripts: [] # Optional - pre-defined script entries
manifest_version (Required)
The manifest schema version. Currently must be "1.0".
manifest_version: "1.0"
metadata (Optional)
Document metadata for identification and documentation.
Field |
Type |
Description |
|---|---|---|
|
string |
System/robot name (e.g., “turtlebot3-nav2”) |
|
string |
Manifest version (e.g., “1.0.0”) |
|
string |
Human-readable description |
metadata:
name: "my-robot"
version: "2.0.0"
description: "Mobile robot with Nav2 navigation stack"
config (Optional)
Discovery behavior configuration.
Field |
Type |
Description |
|---|---|---|
|
string |
Policy for ROS nodes not in manifest |
|
boolean |
Copy topics/services from runtime nodes (default: true) |
|
boolean |
Manifest values can override runtime (default: true) |
unmanifested_nodes options:
ignore- Don’t expose unmanifested nodeswarn- Log warning, include as orphans (default)error- Fail startup if orphan nodes detectedinclude_as_orphan- Include withsource: "orphan"
config:
unmanifested_nodes: warn
inherit_runtime_resources: true
allow_manifest_override: true
Areas
Areas represent logical or physical groupings (subsystems, locations, etc.). In runtime-only mode, areas are derived from ROS 2 namespaces.
Note
The areas: section is optional. For simple robots without subsystem hierarchy,
you can omit areas entirely and use components as the top-level entities. See
Flat Entity Tree below.
Schema
areas:
- id: string # Required - unique identifier
name: string # Required - human-readable name
namespace: string # Optional - ROS 2 namespace path
category: string # Optional - classification
description: string # Optional - detailed description
tags: [string] # Optional - tags for filtering
translation_id: string # Optional - i18n key
parent_area_id: string # Optional - parent area reference
subareas: [] # Optional - nested area definitions
Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique identifier (alphanumeric, hyphens allowed) |
|
string |
Yes |
Human-readable name |
|
string |
No |
ROS 2 namespace path (e.g., “/perception”) |
|
string |
No |
Classification for filtering |
|
string |
No |
Detailed description |
|
[string] |
No |
Tags for filtering and grouping |
|
string |
No |
Internationalization key |
|
string |
No |
Parent area ID (for flat hierarchy definition) |
|
[Area] |
No |
Nested area definitions |
Example
areas:
- id: perception
name: "Perception Subsystem"
category: "sensor-processing"
description: "Sensor data acquisition and processing"
tags:
- sensors
- realtime
subareas:
- id: lidar-processing
name: "LiDAR Processing"
description: "Point cloud processing pipeline"
- id: camera-processing
name: "Camera Processing"
description: "Image processing pipeline"
- id: navigation
name: "Navigation Subsystem"
category: "motion-planning"
Components
Components represent hardware or virtual entities (ECUs, sensors, controllers). In runtime-only mode, synthetic components are created per namespace to group Apps (nodes). In manifest mode, components are explicitly defined and Apps are linked to them.
Schema
components:
- id: string # Required - unique identifier
name: string # Required - human-readable name
type: string # Optional - component type
category: string # Optional - classification
area: string # Optional - reference to area.id
namespace: string # Optional - ROS 2 namespace
fqn: string # Optional - fully qualified name
variant: string # Optional - hardware variant
description: string # Optional - detailed description
tags: [string] # Optional - tags for filtering
translation_id: string # Optional - i18n key
parent_component_id: string # Optional - parent component
depends_on: [string] # Optional - component IDs this depends on
subcomponents: [] # Optional - nested definitions
external: boolean # Optional - non-ROS external asset (default: false)
identity: # Optional - asset-identity nameplate
manufacturer: string # Vendor / manufacturer name
model: string # Product designation / order code
serial_number: string # Unit serial number
hardware_revision: string # Hardware revision
firmware_version: string # Firmware version
software_version: string # Software/application version
network_endpoint: string # e.g. "opc.tcp://plc.local:4840"
role: string # Functional role (e.g. "plc", "drive")
extra: # Optional - vendor-specific extras
<key>: string # Free-form string map (rack/slot, MAC, asset tag, ...)
lock: # Optional - per-entity lock configuration
required_scopes: [string] # Collections requiring a lock before mutation
breakable: boolean # Whether locks can be broken (default: true)
max_expiration: integer # Max lock TTL in seconds (0 = global default)
Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique identifier |
|
string |
Yes |
Human-readable name |
|
string |
No |
Component type (sensor, actuator, controller, etc.) |
|
string |
No |
Classification for filtering |
|
string |
No |
Parent area ID |
|
string |
No |
ROS 2 namespace path |
|
string |
No |
Fully qualified name (namespace + id) |
|
string |
No |
Hardware variant identifier |
|
string |
No |
Detailed description |
|
[string] |
No |
Tags for filtering |
|
string |
No |
Internationalization key |
|
string |
No |
Parent component ID |
|
[string] |
No |
List of component IDs this component depends on |
|
[Component] |
No |
Nested component definitions |
|
boolean |
No |
True if the component is a non-ROS external asset (PLC, fieldbus device,
any asset a protocol plugin bridges into SOVD). Tri-state: omitting
|
|
object |
No |
Asset-identity nameplate of the asset behind the component. All keys
are optional strings ( |
Common Component Types
sensor- Sensors (LiDAR, camera, IMU)actuator- Actuators (motors, grippers)controller- Controllers (main computer, ECU)accelerator- Compute accelerators (GPU, TPU)communication- Communication interfacespower- Power management
Example
components:
- id: main-computer
name: "Main Computer"
type: "controller"
area: control
description: "Raspberry Pi 4 running ROS 2"
variant: "rpi4-8gb"
subcomponents:
- id: gpu-unit
name: "GPU Processing Unit"
type: "accelerator"
- id: lidar-sensor
name: "LiDAR Sensor"
type: "sensor"
area: perception
description: "360° laser range finder"
tags:
- safety-critical
- realtime
- id: imu-sensor
name: "IMU Sensor"
type: "sensor"
area: perception
# External device component (not a ROS node); owns its faults by entity id
- id: line-plc
name: "Line PLC"
external: true
Assets
Assets declare manually inventoried equipment that no protocol layer can
describe (or fully describe): unnetworked devices, third-party hardware, spare
nameplate data. Each asset becomes a Component with source: "inventory" and
a structured asset identity carrying per-field provenance "inventory", and
merges into the entity tree by id alongside protocol-discovered structure.
In the identity merge, inventory ranks below manifest and live protocol
reads but above runtime guesses.
Schema
assets:
- id: string # Required - stable asset id (merge key)
manufacturer: string # Optional - vendor / OEM
model: string # Optional - model / order code
serial: string # Optional - serial number
hardware_rev: string # Optional - hardware revision
firmware: string # Optional - firmware / software version
endpoint: string # Optional - network endpoint (URL / host:port)
role: string # Optional - functional role
area: string # Optional - Area id placing the asset in the tree
namespace: string # Optional - operator-declared placement (sets fqn)
name: string # Optional - display name (default: "<manufacturer> <model>")
description: string # Optional - detailed description
variant: string # Optional - hardware variant identifier
type: string # Optional - component type
translation_id: string # Optional - internationalization key
parent_component_id: string # Optional - parent component ID
depends_on: [string] # Optional - component IDs this asset depends on
tags: [string] # Optional - tags for filtering
external: boolean # Optional - non-ROS external asset (default: false)
any_other_key: string # Kept verbatim as an identity extra
Fields
The identity keys accept the same aliases as the CSV import:
serial_number for serial, hardware_revision / hw_rev for
hardware_rev, and firmware_version / fw for firmware. Aliased
keys land on the typed identity fields, not in the extras. Any scalar key not
listed above is preserved as an identity extra.
external classifies the asset as a non-ROS device, the same tri-state field
as on a components: entry. An external asset with no bound child apps owns
its fault scope by its own entity id. It is a recognized key, so it classifies
the Component instead of being kept as an identity extra.
Placement is optional: without area (and namespace) the asset is
reachable at /components/{id} and in the flat component list, but does not
appear under any Area. An area must reference an area defined in the
manifest, otherwise validation fails (rule R006).
Example
areas:
- id: cell-3
name: "Cell 3"
assets:
- id: hyd-pump-2
manufacturer: Grundfos
model: CR-5
serial_number: "GP-2214-0087"
area: cell-3
role: pump
rack: R2 # kept as identity extra "rack"
CSV Inventory Import
The same asset entries can be bulk-imported from a CSV file via the
discovery.inventory.csv_path gateway parameter (requires a manifest-backed
discovery mode: manifest_only, or hybrid with
discovery.manifest_path set; empty = disabled). The CSV is re-read on every
manifest load / reload and appended to the merged manifest before validation.
Columns: the header row is matched case-insensitively after trimming. Canonical columns are
id(required),manufacturer,model,serial,hardware_rev,firmware,endpoint,roleandarea, with the same aliases as theassets:list. Any other column is kept as an identity extra keyed by its original header.Quoting: RFC-4180-style; double-quoted fields may contain commas, newlines and escaped quotes (
""). Unquoted fields are whitespace-trimmed; a UTF-8 BOM (Excel “CSV UTF-8” export) is stripped.Size cap: the file is rejected before reading if it exceeds 1 MiB.
Row policy: rows without an
idare skipped with a warning; for duplicate ids within the CSV the first row wins. A row whose id is already a manifest component keeps the manifest definition and folds the row’s identity in as gap-fill; a row whose id collides with any other manifest entity is skipped, and an unknownareavalue is dropped (asset kept, placement-less). None of these fail the load. A missing file is skipped with a warning; an unreadable or malformed file (e.g. noidcolumn) fails the load.
Apps
Apps represent software applications, typically mapping 1:1 to ROS 2 nodes. Apps exist only in manifest and hybrid modes.
Schema
apps:
- id: string # Required - unique identifier
name: string # Required - human-readable name
category: string # Optional - classification
is_located_on: string # Optional - component ID
depends_on: [string] # Optional - app IDs this app depends on
description: string # Optional - detailed description
tags: [string] # Optional - tags for filtering
translation_id: string # Optional - i18n key
external: boolean # Optional - not a ROS node (default: false)
ros_binding: # Required for hybrid mode linking
node_name: string # Required - ROS node name
namespace: string # Optional - namespace (default: /)
topic_namespace: string # Optional - match by topic prefix
lock: # Optional - per-entity lock configuration
required_scopes: [string] # Collections requiring a lock before mutation
breakable: boolean # Whether locks can be broken (default: true)
max_expiration: integer # Max lock TTL in seconds (0 = global default)
Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique identifier |
|
string |
Yes |
Human-readable name |
|
string |
No |
Classification for filtering |
|
string |
No |
Component ID where app runs |
|
[string] |
No |
List of app IDs this app depends on |
|
string |
No |
Detailed description |
|
[string] |
No |
Tags for filtering |
|
string |
No |
Internationalization key |
|
boolean |
No |
True if not a ROS node (treated as not external when omitted). The
classification is tri-state internally: omitting |
ros_binding Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes* |
ROS 2 node name to bind to |
|
string |
No |
Namespace (“*” for wildcard) |
|
string |
Yes* |
Alternative: match by topic prefix |
* Either node_name or topic_namespace is required.
Matching behavior:
Name and namespace match (default):
node_namemust match exactly.namespaceuses path-segment-boundary matching:/navmatches/navand/nav/subbut NOT/navigation.Wildcard namespace: Set
namespace: "*"to match node in any namespaceTopic namespace: Match nodes by their published topic prefix
Example
apps:
# Match by exact node name and namespace
- id: lidar-driver
name: "LiDAR Driver"
is_located_on: lidar-sensor
ros_binding:
node_name: velodyne_driver
namespace: /sensors
# Match node in any namespace
- id: camera-driver
name: "Camera Driver"
ros_binding:
node_name: usb_cam
namespace: "*"
# Match by topic namespace
- id: perception-pipeline
name: "Perception Pipeline"
ros_binding:
topic_namespace: /perception
# App with dependencies
- id: slam-node
name: "SLAM Node"
category: "localization"
is_located_on: main-computer
depends_on:
- lidar-driver
- imu-driver
ros_binding:
node_name: slam_toolbox
namespace: /mapping
# External app (not a ROS node)
- id: cloud-connector
name: "Cloud Connector"
external: true
description: "External cloud service integration"
Functions
Functions represent high-level capabilities spanning multiple apps. Functions are always manifest-defined and aggregate data from their host apps.
Schema
functions:
- id: string # Required - unique identifier
name: string # Required - human-readable name
category: string # Optional - classification
hosted_by: [string] # Required - list of app IDs
depends_on: [string] # Optional - function IDs
description: string # Optional - detailed description
tags: [string] # Optional - tags for filtering
translation_id: string # Optional - i18n key
Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique identifier |
|
string |
Yes |
Human-readable name |
|
[string] |
Yes |
List of app IDs that implement this function |
|
string |
No |
Classification for filtering |
|
[string] |
No |
List of function IDs this function depends on |
|
string |
No |
Detailed description |
|
[string] |
No |
Tags for filtering |
|
string |
No |
Internationalization key |
Function Capabilities
Functions aggregate capabilities from their host apps:
Data: Combined topics from all host apps
Operations: Combined services/actions from all host apps
Faults: Faults from all host apps
Example
functions:
- id: autonomous-navigation
name: "Autonomous Navigation"
category: "mobility"
description: "Complete autonomous navigation capability"
tags:
- safety-critical
- autonomous
hosted_by:
- amcl-node
- planner-server
- controller-server
- bt-navigator
- id: localization
name: "Localization"
category: "state-estimation"
hosted_by:
- amcl-node
- map-server
- id: perception
name: "Environment Perception"
category: "sensing"
hosted_by:
- lidar-driver
- camera-driver
- point-cloud-processor
Scripts
Scripts define pre-deployed diagnostic scripts that are available on entities.
Scripts defined in the manifest are managed - they cannot be deleted via the REST API.
Note
Manifest scripts are only loaded when scripts.scripts_dir is configured in the
gateway parameters. Without it, the scripts: block is parsed but scripts are
not exposed via the REST API.
Schema
scripts:
- id: string # Required - unique identifier
name: string # Optional - human-readable name (defaults to id)
description: string # Optional - detailed description
path: string # Required - filesystem path to script file
format: string # Required - execution format (bash, python, sh)
timeout_sec: integer # Optional - execution timeout (default: 300)
entity_filter: [string] # Optional - glob patterns for entity matching
env: # Optional - environment variables
KEY: "value"
args: # Optional - argument definitions
- name: string
type: string
flag: string
parameters_schema: # Optional - JSON Schema for parameters
Fields
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique script identifier |
|
string |
No |
Human-readable name (defaults to id) |
|
string |
No |
Detailed description |
|
string |
Yes |
Filesystem path to the script file |
|
string |
Yes |
Execution format: |
|
integer |
No |
Max execution time in seconds (default: 300) |
|
[string] |
No |
Glob patterns for entity matching (e.g., |
|
map |
No |
Environment variables passed to the script |
|
[object] |
No |
Argument definitions with |
|
object |
No |
JSON Schema for execution parameters validation. Nested objects and arrays are fully supported. |
Example
scripts:
- id: run-diagnostics
name: "Run Diagnostics"
description: "Check health of all sensors"
path: "/opt/scripts/run-diagnostics.sh"
format: "bash"
timeout_sec: 30
entity_filter:
- "components/*"
env:
GATEWAY_URL: "http://localhost:8080"
- id: calibrate-sensor
name: "Calibrate Sensor"
path: "/opt/scripts/calibrate.py"
format: "python"
timeout_sec: 60
args:
- name: threshold
type: float
flag: "--threshold"
See also
See the Scripts section in REST API Reference for API endpoints.
Flat Entity Tree
For simple robots where the entire system is a single unit, you can omit the
areas: section and use a flat component tree instead. The top-level component
represents the robot itself, with subcomponents for hardware modules:
manifest_version: "1.0"
metadata:
name: "flat-turtlebot"
version: "1.0.0"
description: "TurtleBot3 without area hierarchy"
# No areas section - components are top-level entities
components:
- id: turtlebot3
name: "TurtleBot3 Burger"
type: "mobile-robot"
- id: raspberry-pi
name: "Raspberry Pi 4"
type: "controller"
parent_component_id: turtlebot3
- id: lds-sensor
name: "LDS-02 LiDAR"
type: "sensor"
parent_component_id: turtlebot3
apps:
- id: lidar-driver
name: "LiDAR Driver"
is_located_on: lds-sensor
ros_binding:
node_name: ld08_driver
namespace: /
For manifest-based discovery (manifest_only or hybrid), simply omit the
areas: section as shown above - no additional configuration is needed.
In runtime-only discovery, Areas are never created - they come from manifest
only. A complete example is available at
config/examples/flat_robot_manifest.yaml in the gateway package.
Complete Example
Here’s a complete manifest for a TurtleBot3 robot:
manifest_version: "1.0"
metadata:
name: "turtlebot3-nav2"
version: "2.0.0"
description: "TurtleBot3 with Nav2 navigation stack"
config:
unmanifested_nodes: warn
inherit_runtime_resources: true
areas:
- id: perception
name: "Perception"
category: "sensor-processing"
- id: navigation
name: "Navigation"
category: "motion-planning"
- id: control
name: "Control"
category: "motion-control"
components:
- id: lidar-sensor
name: "LiDAR Sensor"
type: "sensor"
area: perception
- id: main-computer
name: "Main Computer"
type: "controller"
area: control
apps:
- id: lidar-driver
name: "LiDAR Driver"
is_located_on: lidar-sensor
ros_binding:
node_name: ld08_driver
- id: amcl-node
name: "AMCL Localization"
category: "localization"
is_located_on: main-computer
ros_binding:
node_name: amcl
- id: planner-server
name: "Planner Server"
category: "navigation"
is_located_on: main-computer
depends_on:
- amcl-node
ros_binding:
node_name: planner_server
functions:
- id: autonomous-navigation
name: "Autonomous Navigation"
category: "mobility"
hosted_by:
- amcl-node
- planner-server
scripts:
- id: run-diagnostics
name: "Run Diagnostics"
path: "/opt/scripts/diagnostics.sh"
format: "bash"
timeout_sec: 30
entity_filter:
- "components/*"
Validation
Manifests are validated during loading. The validator checks:
Required fields:
manifest_versionmust be present and equal to “1.0”All entities must have
idandnameApps with
ros_bindingmust havenode_nameortopic_namespaceFunctions must have at least one entry in
hosted_byScripts must have
id,path, andformatformatmust be one of:bash,python,sh
References:
areareferences must point to valid area IDsis_located_onmust point to valid component IDsdepends_onmust point to valid app/function IDshosted_bymust point to valid app IDs
Uniqueness:
All entity IDs must be unique within their type
IDs must be unique across all entity types (areas, components, apps, functions, scripts)
Format:
IDs should contain only alphanumeric characters and hyphens
IDs should not start with numbers
Validation errors are reported with the path to the invalid field:
Validation error at apps[2].ros_binding: 'node_name' or 'topic_namespace' required
Validation error at functions[0].hosted_by[1]: App 'unknown-app' not found
See also
Manifest-Based Discovery - User guide for manifest-based discovery
Migration Guide: Runtime to Hybrid Mode - Migration guide