Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions
Dependant Packages
Launch files
Messages
Services
Plugins
Recent questions tagged autoware_component_interface_admission at Robotics Stack Exchange
Package Summary
| Version | 1.10.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-10-07 |
| 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
Changelog for package autoware_component_interface_admission
1.10.0 (2026-09-28)
-
chore: align package versions to 1.9.0 and reset changelogs
-
Merge remote-tracking branch 'origin/main' into tmp/bot/bump_version_base
-
feat(autoware_component_interface_admission): deploy-time interface admission with spec QoS conformance (#1317)
* feat(autoware_component_interface_admission): add the interface admission gate package Reimplementation target of the universe proof of concept, now core-resident so the deploy-time admission gate builds against the released core without depending on autoware_component_interface_specs. The universe fork pull request is superseded by this port.
* feat(autoware_component_interface_admission): parse QoS-carrying v2 manifests and the spec QoS table A v2 manifest entry may carry a [qos]{.title-ref} block (reliability / durability / depth) and may omit its version fields entirely, so has_qos and has_version now record what the source document actually declared instead of letting an absent field default silently. The version fields stay an all-or-none group: a partial declaration is a malformed document and throws. spec_qos_from_json() parses autoware_component_interface_specs' interface_manifest.json into the interface_name -> QosRecord table the deploy gate holds every endpoint to. It requires a top-level [interfaces]{.title-ref} array whose every entry carries both [interface]{.title-ref} and `qos`: accepting an unrelated document would hand back an empty table, which would disable the conformance check for every endpoint without saying so.
* feat(autoware_component_interface_admission): require every endpoint to use the QoS its spec declares A specification that declares 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. So the declared reliability and durability are an exact requirement for both sides: an endpoint that deviates -- in either direction, since offering TRANSIENT_LOCAL where the spec says VOLATILE is still not what consumers were told to expect -- is reported as QOS_SPEC_MISMATCH. The check is per endpoint, not per pairing: conformance is a property of a single endpoint and its spec, so a publisher-only image with no consumer anywhere in the deploy set, and a second provider a stage-1 match never picked, are each exactly as checkable as a matched pair. For an interface the spec set declares nothing about (a vendor or out-of-tree interface) there is nothing to hold either side to, so the gate falls back to a direct offered-vs-requested DDS check on the one stage-1-matched pair (QOS_PAIR_INCOMPATIBLE). That catches a pairing which cannot connect at all; it does not pretend to enforce a specification that does not exist. depth is endpoint-local and never enters a verdict, and an out-of-vocabulary policy string matches nothing and ranks incomparable, so both paths fail closed.
* feat(autoware_component_interface_admission): add manifest_admit's --spec-manifest option and array-root fragments manifest_admit gains an optional [--spec-manifest <interface_manifest.json>]{.title-ref} (repeatable; the last occurrence wins), which loads the spec QoS table so every provided / required entry carrying [qos]{.title-ref} is held to what its specification declares. Omitting it writes a warning to stderr rather than quietly skipping the check: for a safety-adjacent gate, a deploy whose endpoints deviate from their specs must never exit 0 in silence. Each positional file may now hold either a single manifest document or a JSON array of them, which is the on-disk shape of a multi-node package's installed fragment. A malformed element inside an array fails closed exactly like a malformed single-document file. main() moves into run_manifest_admit() so the argument parsing and the exit-code contract are exercised directly by unit tests instead of by spawning a process; manifest_admit.cpp is now a thin argv/stdout/stderr wrapper. ---------
-
Contributors: Yutaka Kondo, github-actions