Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 0.52.0 |
| License | Apache License 2.0 |
| Build type | AMENT_CMAKE |
| Use | RECOMMENDED |
Repository Summary
| Checkout URI | https://github.com/autowarefoundation/autoware_core.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-08-17 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Package Description
Maintainers
- Yutaka Kondo
Authors
autoware_component_interface_admission
The shared component-interface admission rule and the deploy-time manifest gate for Autoware’s component interface versioning. This is a standalone, ROS-message-free leaf package: it depends only on the ament build system and nlohmann-json (no rclcpp, no autoware_component_interface_specs), so it builds against today’s released core.
One rule, two triggers
Interface compatibility is enforced by a single admission rule — “the consumer’s accepted MAJOR range contains the provider’s MAJOR” plus a remap-safe two-layer name match — evaluated at two triggers:
-
Deploy-time (primary): each component bakes its interface manifest into its container image, and a pre-boot gate cross-checks the whole composed image set, rejecting an incompatible combination before anything is built, pulled, or booted. This package provides that gate (
evaluate_deploy()+ themanifest_admitCLI). -
Runtime (secondary, not yet implemented): the same rule at component startup over a broadcast manifest. This package provides the rule (
evaluate()); the runtime broadcast and checker are not implemented yet (see the deferred-work note below).
Both triggers live in admission_rule.hpp and share the same version-compatibility rule: the deploy trigger applies stage 1 (version + interface_name), and the runtime trigger adds stage 2 (the remap-resolved resolved_name match). One rule, evaluated at the depth each trigger can see — not a parallel reimplementation.
Admission rule
For each required interface, the rule finds providers of the same interface_name and applies a two-layer match (evaluate() in include/autoware/component_interface_admission/admission_rule.hpp):
| Situation | Verdict | Code |
|---|---|---|
version-ok and resolved_name coincide (the actually-wired provider) |
ACCEPTED |
0 |
MAJOR in range but min_minor unmet |
MINOR_MISMATCH |
2 |
| MAJOR out of the accepted range | MAJOR_MISMATCH |
1 |
version-ok but a remap left resolved_name disjoint |
TOPIC_MISMATCH |
3 |
| required interface has no provider in the set | NO_PROVIDER |
4 |
The MINOR bound is inclusive (provider.minor >= min_minor), and min_minor == 0 means unconstrained. Because MINOR resets to 0 on every MAJOR bump (semver), min_minor binds only at the MAJOR it was declared against (accept_major_min); at any higher accepted MAJOR the bound is already satisfied. So a consumer accepting [2, 3] with min_minor = 5 admits provider 2.5 and 3.0, but rejects 2.4 as a MINOR_MISMATCH. Among several version-compatible providers, the one whose resolved_name coincides is preferred (the wired provider); a version-compatible provider left on a disjoint wire topic by a remap is the false-accept that logical-name-only matching would miss, reported as TOPIC_MISMATCH.
Deploy vs runtime: NO_PROVIDER is deploy-only
The one place the two triggers differ is a required interface with no provider:
-
Runtime (
evaluate()): such a required interface is skipped — under the runtime trigger a provider may simply not have started yet, so absence is not yet a failure. -
Deploy-time (
evaluate_deploy()): the image set is complete, so a required interface with no provider anywhere in the set is a hardNO_PROVIDERrejection.
NO_PROVIDER is a completeness verdict, not a version verdict: it fires whenever a required entry — versioned or not — has no provider of its interface_name anywhere in the set at all, matching the pre-v2 behavior where every required entry got this check. For a required entry that DOES declare a version, it additionally fires when every provider that exists is itself unversioned (has_version == false), since none of them is then version-checkable; an unversioned required entry has no version bounds to check in the first place, so it is satisfied by any provider regardless of that provider’s own has_version — two sides both declining a version claim is a coherent unversioned pairing, not a gap to reject.
A required entry declared without a version at all (has_version == false) never produces MAJOR_MISMATCH / MINOR_MISMATCH at either trigger, since version bounds simply do not apply. But TOPIC_MISMATCH is a wiring verdict, not a version one, so has_version does not suppress it: at the runtime trigger, an unversioned required entry is still checked against stage 2 exactly like a versioned one — a remap that leaves it on a disjoint wire topic is still caught, rather than silently accepted because no version comparison was in play.
The deploy-time gate matches on version + interface_name only (stage 1). The remap-resolved resolved_name match (stage 2 of the rule) is runtime-only, because remaps live in the launch / compose layer and are not visible in image metadata — so evaluate_deploy() never inspects resolved_name and never emits TOPIC_MISMATCH. That residual remap false-accept is exactly what the runtime trigger backstops.
QoS verdicts (deploy-time only)
A v2 manifest entry may also carry a qos block (reliability, durability, depth; see the JSON schema below). The QoS an interface’s specification declares (from autoware_component_interface_specs’ interface_manifest.json, parsed by spec_qos_from_json()) is an exact requirement for both sides, not a bound either side may deviate from. A specification that says RELIABLE means the interface is carried without drops or reordering; a subscription that quietly requests BEST_EFFORT still connects under DDS’s request-vs-offered rule but no longer gets that property, and preventing exactly that class of mistake is what declaring the QoS in the specification is for. Deviating in the “stronger” direction (TRANSIENT_LOCAL where the spec says VOLATILE) is rejected on the same grounds: it is still not what every consumer written against the specification was told to expect.
How that is checked depends on whether the spec set declares a QoS for the interface at all:
-
With a declared QoS:
evaluate_deploy()requires everyprovidedentry and everyrequiredentry that carriesqosto use exactly thatreliabilityanddurability, independently of pairing — a publisher-only image with no consumer anywhere in the set, or a second provider of the same interface that a stage-1 match never picked, is exactly as checkable as a matched pair, because conformance is a property of the single endpoint and its spec. Such a verdict names only the one side it is about (seeAdmissionResult, below). -
Without a declared QoS (e.g. a vendor / out-of-tree interface): there is nothing to hold each endpoint to in isolation, so the gate falls back to a direct offered-vs-requested DDS compatibility check on the one stage-1-matched pairing, and only when both sides of that pairing carry
qos. This catches a pairing that cannot connect at all; it does not, and cannot, enforce a specification that does not exist.
| Situation | Verdict | Code | Per-endpoint or per-pair |
|---|---|---|---|
the spec set declares a QoS for this interface and an endpoint’s qos differs from it |
QOS_SPEC_MISMATCH |
5 | per-endpoint |
| the spec set declares no QoS and the one stage-1-matched pairing’s offered/requested QoS is directly incompatible | QOS_PAIR_INCOMPATIBLE |
6 | per-pair |
A provider-side and a consumer-side QOS_SPEC_MISMATCH for the same interface are reported as two separate rows (AdmissionResult leaves the other side’s node field empty for that code), never merged into one.
depth is endpoint-local and presentational only: it never participates in a verdict, on either path above. A required entry declared without a version at all (has_version == false in a v2 document) never produces a version verdict, but a provider is still resolved for it by interface_name alone (if one exists) so the pairwise QoS fallback has a pairing to check; if none exists, it is NO_PROVIDER like any other required entry (see above). reliability_rank() / durability_rank() in admission_rule.hpp express DDS’s own offered-vs-requested strength order on this package’s JSON string encoding, and are used only by that fallback — they never relax the exact requirement above. An out-of-vocabulary policy string ranks as incomparable and matches nothing (fail closed): every comparison against it fails.
Records and JSON schema
records.hpp defines plain C++ structs that mirror the (future) handshake message set field-for-field, so the eventual rosidl binding is mechanical:
ProvidedInterface { ns, interface_name, resolved_name, type_name, major, minor, patch, has_version, has_qos, qos }RequiredInterface { ns, interface_name, resolved_name, type_name, accept_major_min, accept_major_max, min_minor, has_version, has_qos, qos }InterfaceManifest { owner, node_name, provided[], required[] }-
QosRecord { reliability, durability, depth }—reliabilityis"reliable"or"best_effort";durabilityis"volatile"or"transient_local".
interface_name is the spec-declared name (Spec::name), remap-invariant and the matching key; resolved_name is the remap-resolved fully-qualified name, equal to interface_name when not remapped.
manifest_json.hpp serializes a manifest to / parses it from this JSON payload (the OCI-label payload schema below; this is the v1 shape, where the version fields are always present and qos is absent):
{
"owner": "autowarefoundation",
"node_name": "/perception/detection",
"provided": [
{
"ns": "perception",
"interface_name": "/perception/object_recognition/objects",
"resolved_name": "/perception/object_recognition/objects",
"type_name": "autoware_perception_msgs/msg/PredictedObjects",
"major": 2,
"minor": 1,
"patch": 0
}
],
"required": [
{
"ns": "map",
"interface_name": "/map/vector_map",
"resolved_name": "/map/vector_map",
"type_name": "autoware_map_msgs/msg/LaneletMapBin",
"accept_major_min": 1,
"accept_major_max": 2,
"min_minor": 0
}
]
}
File truncated at 100 lines see the full file