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
# (deprecated alias for this key: "discovery:")
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
Note
Unknown top-level keys are ignored, and the gateway says so. Any
top-level key outside the set the parser reads
(manifest_version, metadata, config, discovery, areas,
components, assets, apps, functions, scripts,
capabilities) is skipped, and the gateway logs a warning naming the key
and listing the ones it does know. If a whole block of your manifest seems
to have no effect, that log line is the first place to look - a misspelled
or misplaced top-level key is the usual cause.
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.
Every setting in this block describes how the manifest is combined with what
runtime discovery finds, so none of them does anything in manifest_only
(which parses the block and then runs without the merge pipeline that reads
it) or in runtime_only (which has no manifest at all).
Within hybrid mode they differ in what else they need.
unmanifested_nodes and inherit_runtime_resources are applied by the
runtime linker, which only exists when the runtime layer is enabled.
allow_manifest_override is applied to the manifest layer’s own policies,
so it takes effect whenever the manifest layer is built - though with no
runtime layer to merge against there is nothing for it to change.
Note
config: is the canonical top-level key. discovery: is accepted as a
deprecated alias for the same block: it is read, the gateway logs a
deprecation warning naming it, and it will be removed in a future release.
A manifest that declares both keys uses config: and ignores
discovery:; both facts are logged.
Field |
Type |
Description |
|---|---|---|
|
string |
Policy for running ROS nodes that the manifest does not declare
(default: |
|
boolean |
Copy the bound node’s topics, services and actions onto the linked manifest app (default: true) |
|
boolean |
Let manifest values outrank runtime values in the merge (default:
true). |
unmanifested_nodes
A node is unmanifested (an orphan) when it is present in the ROS graph and no manifest app binds to it.
Value |
Behaviour |
|---|---|
|
Hide them. Every app that did not come from the manifest, the manual asset inventory or a plugin is dropped from the entity tree, and heuristic Components and Areas sitting in a namespace taken from one of the orphan node FQNs are dropped with them. Note the reach: this suppresses every non-manifest app, not only the ones that were classified as orphans. |
|
Default. The nodes stay in the entity tree; the gateway logs one warning per orphan node. |
|
The nodes stay in the entity tree, exactly as under |
|
The same entity tree as |
The four values are lower-case. Warn is not warn: it is an
unrecognised value and is treated as one.
An unrecognised value raises validation warning R014, and the parsed
policy falls back to warn. Because discovery.manifest_strict_validation
defaults to true and turns validation warnings into a load failure, a typo
here rejects the manifest by default; with strict validation off the manifest
loads, runs on warn, and the gateway logs the rejected value along with
the list of valid ones.
Malformed structure: R015
R014 is about a value the parser understands the shape of but not the
content of. R015 is the other half: the key is there but its YAML kind
is wrong. It is raised when
the
config:block (or the deprecateddiscovery:alias) is not a mapping - a scalar or a sequence; orone of the settings inside it is not the kind it must be:
unmanifested_nodesnot a string,inherit_runtime_resourcesorallow_manifest_overridenot a boolean.
A key that carries no value at all is YAML null and means “not set”. That is
never an error: unmanifested_nodes: with nothing after it loads silently
and the default applies.
Important
Advisory in this release. R014 and R015 are reported on the log
channel only: they do not fail the load, whatever
discovery.manifest_strict_validation is set to. The manifest loads, the
offending block or setting is ignored, and its documented default applies.
This is a deliberate one-release grace period. The settings in this block
were parsed but never read before, so a manifest carrying
unmanifested_nodes: Warn or inherit_runtime_resources: 0 loaded
without complaint; making them validation warnings immediately would refuse
that same file on upgrade under the shipped strict default, taking hybrid
discovery down to runtime_only for a deployment nobody edited.
They become validation warnings in the next release, at which point the
strictness dial does apply and a strict gateway rejects a manifest carrying
either. Fix them now while they are only noisy: grep your gateway log for
[R014] and [R015].
Either way the malformed part costs only itself: the rest of the manifest is parsed normally rather than the whole file being abandoned.
inherit_runtime_resources
When a manifest app is linked to a running node, the default (true) copies
that node’s topics, services and actions onto the app, so its /data and
/operations collections describe what the node really exposes.
With false, the link itself is unchanged - the app still reports
is_online and still names the node it is bound to - but the copy does not
happen, so the app exposes only the resources the manifest declares for it.
An app that declares none therefore serves empty /data and
/operations collections while still being reported as online.
This holds however the app is named. An app whose id equals the bound
node’s name is one entity by id with the runtime layer’s view of that node, so
the merge would otherwise fold the node’s live topics and services in before
linking runs; the declared collections are restored after the merge precisely
so the flag means the same thing in both spellings.
allow_manifest_override
What it means. With the shipped manifest and runtime layers, this flag
controls exactly one thing: whether the manifest claims exclusivity over the
hierarchy. true (the default) means a declared parent-child relationship is
the whole truth and runtime-discovered members are dropped. false means the
manifest stops claiming exclusivity, so runtime-discovered hosts and children
join the union instead of being discarded.
Against that layer pair it changes nothing else. (A discovery plugin is a
third layer and widens this - see Mechanism below.) In particular it does
not change
an entity’s provenance: x-medkit.source keeps naming the layer that
declared the entity under every layer policy, because provenance is what the
gateway keys its delete decisions on and is never negotiated between layers.
If you need per-field-group control, do not reach for this flag - it is a
blunt switch. Use discovery.merge_pipeline.layers.manifest.<group> in the
gateway parameters, which sets one group at a time; the field groups and the
policy values are listed under
Merge Policies. That page documents the
parameters themselves and does not discuss this flag.
Mechanism. The flag is shorthand for the per-field-group merge policies of
the manifest layer: false demotes all five groups - identity, hierarchy,
live_data, status, metadata - to fallback, so the manifest layer only fills
gaps the runtime layer leaves. Four of those five demotions are inert against
the shipped runtime layer, which is why the visible result is narrower than the
mechanism suggests:
hierarchy - the one that shows. Collection fields (
depends_on, a Function’shosted_bylist) become the union of the manifest’s and the runtime layer’s, rather than the manifest’s alone. The scalar hierarchy fields are unchanged: an App’sis_located_on, a Component’sarea,parent_component_id,fqnandnamespace, and an Area’snamespaceandparent_area.identity, live_data, status - no change. The runtime layer is already
fallbackfor identity and alreadyauthoritativefor live data and status, and a scalar merge behaves identically whether the manifest layer outranks the runtime layer or ties with it.metadata - no change, because
sourceis the only metadata field the runtime layer contributes and provenance is exempt from the policy. This demotion becomes visible only when a discovery plugin contributes metadata of its own: avariantor an external-entity classification from the plugin layer then wins over the manifest’s.
Precedence. The blanket demotion is applied before the per-group gateway
parameters, so an explicitly configured
discovery.merge_pipeline.layers.manifest.<group> wins over
allow_manifest_override: false for that one group; the other four stay
demoted. Pinning hierarchy back to authoritative therefore restores
manifest exclusivity while leaving the rest of the switch in force.
The flag also only matters for an entity that both layers contribute under the same id. An entity that only the manifest declares is unaffected.
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