rosotacom-dev 2.5.dev55__tar.gz → 2.5.dev56__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/cli.py +141 -5
  3. rosotacom_dev-2.5.dev56/src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template +80 -0
  4. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +34 -1
  5. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +4 -4
  6. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +21 -2
  7. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  8. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/SOURCES.txt +1 -0
  9. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/scm_file_list.json +1 -0
  10. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  11. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_packaged_resources.py +39 -0
  12. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_concurrency.py +98 -0
  13. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.devcontainer/devcontainer.json.template +0 -0
  14. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  15. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/CODEOWNERS +0 -0
  16. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  17. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  18. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  19. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  20. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  21. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  22. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  23. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  24. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  25. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/actions/setup-just/action.yml +0 -0
  26. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/dependabot.yml +0 -0
  27. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/benchmark-calibrate.yml +0 -0
  28. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/benchmark-gate.yml +0 -0
  29. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/dev-release.yml +0 -0
  30. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/image-scan.yml +0 -0
  31. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/nightly-e2e.yml +0 -0
  32. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/pr-lightweight.yml +0 -0
  33. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/pr-merge-gate.yml +0 -0
  34. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.github/workflows/release.yml +0 -0
  35. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/.pre-commit-config.yaml +0 -0
  36. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/AGENTS.md +0 -0
  37. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/CONCURRENCY.md +0 -0
  38. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/CONTRIBUTING.md +0 -0
  39. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/DEVELOPMENT_PRINCIPLES.md +0 -0
  40. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/LICENSE.txt +0 -0
  41. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/MANIFEST.in +0 -0
  42. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/README.md +0 -0
  43. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/budgets.jsonl +0 -0
  44. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/calibration/README.md +0 -0
  45. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/calibration/capacity-size-tight-cyclone.json +0 -0
  46. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  47. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/calibration/probe-loss-nominal-cyclone.json +0 -0
  48. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/calibration/probe-loss-tight-cyclone.json +0 -0
  49. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/calibration/probe-loss-tight-fastdds.json +0 -0
  50. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/calibration/probe-loss-tight-zenoh.json +0 -0
  51. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/codecov.yml +0 -0
  52. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/deployment-configuration.md +0 -0
  53. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/benchmark-ab.md +0 -0
  54. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/benchmark-bag-as-load.md +0 -0
  55. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/ci.md +0 -0
  56. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/ffmpeg-keyframes.md +0 -0
  57. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/README.md +0 -0
  58. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  59. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/delay-alone-no-loss.md +0 -0
  60. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
  61. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
  62. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/head-of-line-irregularity.md +0 -0
  63. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  64. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
  65. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/lighter-message-loss.md +0 -0
  66. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/oversubscription-queues-not-losses.md +0 -0
  67. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/reorder-becomes-reader-loss.md +0 -0
  68. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/repro/decode_chain_repro.py +0 -0
  69. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/repro/reorder_drop_repro.py +0 -0
  70. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
  71. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  72. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/forensics-report.md +0 -0
  73. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/geo-link-map.md +0 -0
  74. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/github-repository-settings.md +0 -0
  75. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/link-bandwidth.md +0 -0
  76. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/link-trace.md +0 -0
  77. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/owner-runbook.md +0 -0
  78. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/performance-bands.md +0 -0
  79. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/playbooks/README.md +0 -0
  80. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/quality-model.md +0 -0
  81. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/release-notes/TEMPLATE.md +0 -0
  82. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/release-notes/v2.1.md +0 -0
  83. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/release-notes/v2.2.md +0 -0
  84. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/release-notes/v2.3.md +0 -0
  85. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/release-notes/v2.4.md +0 -0
  86. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/release.md +0 -0
  87. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  88. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/0002-expectation-concepts.md +0 -0
  89. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/0003-metric-backbone.md +0 -0
  90. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/0004-network-profiles.md +0 -0
  91. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  92. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/0006-dynamic-qos.md +0 -0
  93. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/0007-regression-gate.md +0 -0
  94. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/rfcs/README.md +0 -0
  95. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/testing.md +0 -0
  96. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/docs/work-items.md +0 -0
  97. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/install.sh +0 -0
  98. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/justfile +0 -0
  99. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/media/control_center.png +0 -0
  100. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/media/echo_test.png +0 -0
  101. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/media/forensics-report-example.png +0 -0
  102. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/media/host.png +0 -0
  103. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/media/vehicle.png +0 -0
  104. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/pyproject.toml +0 -0
  105. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/requirements.txt +0 -0
  106. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/rosotacom.yaml +0 -0
  107. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/session-configuration.md +0 -0
  108. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/setup.cfg +0 -0
  109. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/__init__.py +0 -0
  110. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/__main__.py +0 -0
  111. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/anonymize.py +0 -0
  112. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/bag_ground_truth.py +0 -0
  113. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/benched_set.py +0 -0
  114. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/benchmark.py +0 -0
  115. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/benchmark_delta.py +0 -0
  116. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/bundle_check.py +0 -0
  117. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/cli_benchmark.py +0 -0
  118. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/deployment.py +0 -0
  119. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/ffmpeg_packet.py +0 -0
  120. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/forensics.py +0 -0
  121. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/geomap.py +0 -0
  122. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/image_cache.py +0 -0
  123. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/netem_dist.py +0 -0
  124. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/network_preflight.py +0 -0
  125. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/network_profiles.py +0 -0
  126. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/network_shaper.py +0 -0
  127. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/plots.py +0 -0
  128. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/py.typed +0 -0
  129. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/benched-set.yaml +0 -0
  130. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/.gitignore +0 -0
  131. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/README.md +0 -0
  132. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/__init__.py +0 -0
  133. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  134. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  135. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
  136. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/profiles.yaml +0 -0
  137. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  138. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  139. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  140. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  141. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  142. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  143. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  144. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  145. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  146. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  147. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  148. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  149. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  150. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  151. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  152. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  153. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  154. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  155. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  156. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  157. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  158. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  159. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  160. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  161. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  162. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  163. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  164. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  165. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  166. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  167. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  168. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  169. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  170. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  171. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  172. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  173. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  174. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  175. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  176. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  177. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  178. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  179. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  180. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  181. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  182. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  183. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  184. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  185. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  186. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  187. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  188. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  189. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  190. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  191. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  192. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  193. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  195. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  196. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  198. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  199. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  200. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  201. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  202. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  203. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  204. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  205. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  206. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  207. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  208. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ros2docker.json.example +0 -0
  209. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  210. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  211. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  212. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  213. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  214. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  215. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  216. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  217. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  218. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  219. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  220. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  221. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
  222. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +0 -0
  223. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  224. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  225. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  226. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  227. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  228. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  229. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  230. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  231. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  232. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  233. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  234. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  235. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  236. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  237. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  238. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  239. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  240. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  241. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  242. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  243. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  244. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  245. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  246. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
  247. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  248. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  249. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  250. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  251. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  252. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  253. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  254. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  255. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  256. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  257. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  258. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  259. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  260. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  261. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  262. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  263. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  264. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  265. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
  266. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
  267. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  268. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  269. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  270. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  271. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  272. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  273. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  274. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  275. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  276. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  277. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  278. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  279. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  280. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  281. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  282. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  283. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  284. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  285. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  286. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  287. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  288. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  289. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  290. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  291. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  292. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  293. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  294. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  295. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  296. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  297. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  298. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  299. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  300. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/status_eval.py +0 -0
  301. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/stream_stats.py +0 -0
  302. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/trace_profiles.py +0 -0
  303. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/transit.py +0 -0
  304. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom/video_quality.py +0 -0
  305. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  306. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  307. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  308. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  309. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/terminology.md +0 -0
  310. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_benched_set_registry.py +0 -0
  311. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_findings.py +0 -0
  312. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_generated_topics_have_a_producer.py +0 -0
  313. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_markdown_links.py +0 -0
  314. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_ota_source_bundle.py +0 -0
  315. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_package_metadata.py +0 -0
  316. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  317. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_pytest_policy.py +0 -0
  318. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_readme_examples.py +0 -0
  319. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_security_maintenance_config.py +0 -0
  320. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_session_configuration_doc.py +0 -0
  321. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_session_template_substitution.py +0 -0
  322. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/contract/test_workflow_contracts.py +0 -0
  323. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/conftest.py +0 -0
  324. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_anonymize_e2e.py +0 -0
  325. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_benchmark_ab.py +0 -0
  326. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_benchmark_capacity.py +0 -0
  327. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_benchmark_replay.py +0 -0
  328. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_parallel_smoke.py +0 -0
  329. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_smoke.py +0 -0
  330. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_timeline_stepping.py +0 -0
  331. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/e2e/test_video_quality_e2e.py +0 -0
  332. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/sessions/generate_rmw_matrix.py +0 -0
  333. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  334. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  335. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  336. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  337. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
  338. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  339. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_anonymize.py +0 -0
  340. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_bag_ground_truth.py +0 -0
  341. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_benched_set.py +0 -0
  342. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_benchmark.py +0 -0
  343. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_benchmark_delta.py +0 -0
  344. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_bundle_check.py +0 -0
  345. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_catmux_pane_logging.py +0 -0
  346. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_cli.py +0 -0
  347. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_cli_benchmark.py +0 -0
  348. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_container_shaping.py +0 -0
  349. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_deployment.py +0 -0
  350. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_ffmpeg_flags.py +0 -0
  351. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_ffmpeg_packet.py +0 -0
  352. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_field_profiles.py +0 -0
  353. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_forensics.py +0 -0
  354. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_geomap.py +0 -0
  355. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_image_cache.py +0 -0
  356. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_link_bytes.py +0 -0
  357. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_link_trace.py +0 -0
  358. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_netem_dist.py +0 -0
  359. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_network_preflight.py +0 -0
  360. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_network_profiles.py +0 -0
  361. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_network_shaper.py +0 -0
  362. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_ota_link_configs.py +0 -0
  363. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_ota_unwrapper.py +0 -0
  364. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_ota_wrapper.py +0 -0
  365. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_ota_xml.py +0 -0
  366. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_playout_pacer.py +0 -0
  367. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_plots.py +0 -0
  368. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_pub_sub_pair_types.py +0 -0
  369. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_qos_fallback_logging.py +0 -0
  370. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_relay_optional_parameters.py +0 -0
  371. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_session_pipeline.py +0 -0
  372. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_stage_latency.py +0 -0
  373. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_status_eval.py +0 -0
  374. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_status_overview.py +0 -0
  375. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_status_overview_observer.py +0 -0
  376. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_stream_stats.py +0 -0
  377. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_trace_profiles.py +0 -0
  378. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_transit.py +0 -0
  379. {rosotacom_dev-2.5.dev55 → rosotacom_dev-2.5.dev56}/tests/unit/test_video_quality.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev55
3
+ Version: 2.5.dev56
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
  import argparse
13
13
  import base64
14
14
  import binascii
15
+ import contextlib
15
16
  import getpass
16
17
  import hashlib
17
18
  import importlib
@@ -23,12 +24,13 @@ import os
23
24
  import re
24
25
  import shlex
25
26
  import shutil
27
+ import signal
26
28
  import subprocess
27
29
  import sys
28
30
  import tarfile
29
31
  import tempfile
30
32
  import time
31
- from collections.abc import Callable, Mapping, Sequence
33
+ from collections.abc import Callable, Iterator, Mapping, Sequence
32
34
  from dataclasses import dataclass, replace
33
35
  from datetime import datetime
34
36
  from pathlib import Path
@@ -2761,12 +2763,49 @@ def _passwordless_watchdog_command(argv: Sequence[str]) -> str:
2761
2763
  return f"nohup sudo -n {shlex.join(argv)} >/dev/null 2>&1 &"
2762
2764
 
2763
2765
 
2766
+ @contextlib.contextmanager
2767
+ def _terminate_via_exception() -> Iterator[None]:
2768
+ """Make SIGTERM unwind the stack instead of ending the process outright.
2769
+
2770
+ The OTA run already has a `finally` that reverts shaping, collects logs and
2771
+ stops both peers. SIGINT reaches it, because Python raises KeyboardInterrupt.
2772
+ SIGTERM does not: the default disposition ends the process immediately, so
2773
+ `finally` never runs and every peer keeps its containers -- and, worse, any
2774
+ netem qdisc stays armed on the peer's own interface, quietly degrading a
2775
+ machine nobody is looking at.
2776
+
2777
+ SIGTERM is not the exotic case. `timeout` sends it, systemd sends it, and a
2778
+ CI job cancelling a step sends it. On 2026-08-19 a run killed by `timeout`
2779
+ left containers behind on both peers, which then blocked the next run's
2780
+ exclusivity check.
2781
+
2782
+ Raising SystemExit gives the same exit status while letting the cleanup run.
2783
+ The handler is restored on the way out so this does not leak into a caller.
2784
+ """
2785
+
2786
+ def handler(signum: int, _frame: object) -> None:
2787
+ raise SystemExit(128 + signum)
2788
+
2789
+ try:
2790
+ previous = signal.signal(signal.SIGTERM, handler)
2791
+ except ValueError:
2792
+ # Not the main thread; the parent already owns signal disposition.
2793
+ yield
2794
+ return
2795
+ try:
2796
+ yield
2797
+ finally:
2798
+ signal.signal(signal.SIGTERM, previous)
2799
+
2800
+
2764
2801
  def _ota_conflict_check(plan: OtaSmokePlan, *, dry_run: bool) -> None:
2765
2802
  """Fail-safe preflight: OTA runs require exclusive control of the peers.
2766
2803
 
2767
- Aborts when a peer already runs rosotacom containers or has active network
2768
- shaping (netem/tbf/htb) on the data interface toward another peer both
2769
- signal an OTA run that is still active or was not cleaned up.
2804
+ Aborts when a peer already runs rosotacom containers, has active network
2805
+ shaping (netem/tbf/htb) on the data interface toward another peer, or is
2806
+ carrying so much unrelated load that a measurement taken on it would not
2807
+ mean anything. The first two signal an OTA run that is still active or was
2808
+ not cleaned up; the third signals a machine somebody else is using.
2770
2809
  """
2771
2810
  from .network_shaper import ota_interface_from_route
2772
2811
 
@@ -2820,6 +2859,59 @@ def _ota_conflict_check(plan: OtaSmokePlan, *, dry_run: bool) -> None:
2820
2859
  )
2821
2860
 
2822
2861
 
2862
+ # A run on a machine somebody else is saturating does not fail — it succeeds
2863
+ # and reports numbers that describe the contention rather than the link. That
2864
+ # is the expensive kind of wrong, because nothing in the artefact says so
2865
+ # afterwards.
2866
+ #
2867
+ # The thresholds are per-CPU, so they read the same on an 8-core laptop and a
2868
+ # 32-thread desktop. At 2.0 the run stops: with twice as many runnable threads
2869
+ # as CPUs, a thread waits on average as long as it runs, which lands directly
2870
+ # in the scheduling latency a transport measurement is trying to resolve.
2871
+ _LOAD_WARN_PER_CPU = 0.7
2872
+ _LOAD_REFUSE_PER_CPU = 2.0
2873
+
2874
+
2875
+ def _ota_load_check(plan: OtaSmokePlan, *, dry_run: bool) -> None:
2876
+ """Refuse to measure on a peer that is busy with someone else's work."""
2877
+ for peer in plan.peers.values():
2878
+ result = _ota_run(
2879
+ peer,
2880
+ "cut -d' ' -f1-3 /proc/loadavg; nproc",
2881
+ label=f"{peer.name}: host load",
2882
+ dry_run=dry_run,
2883
+ batch=True,
2884
+ check=False,
2885
+ )
2886
+ if dry_run or result.returncode != 0:
2887
+ continue
2888
+ lines = (result.stdout or "").split()
2889
+ if len(lines) < 4:
2890
+ continue
2891
+ try:
2892
+ load1, load5, load15 = (float(value) for value in lines[:3])
2893
+ cpus = max(1, int(lines[3]))
2894
+ except ValueError:
2895
+ continue
2896
+ # load5 as well as load1: a run takes minutes, and a machine that was
2897
+ # busy a moment ago is about to be busy again.
2898
+ ratio = max(load1, load5) / cpus
2899
+ # Two decimals to match /proc/loadavg and uptime, so the number in an
2900
+ # artefact can be compared against what the machine reports itself.
2901
+ summary = f"load {load1:.2f}/{load5:.2f}/{load15:.2f} on {cpus} CPUs ({ratio:.2f} per CPU)"
2902
+ if ratio >= _LOAD_REFUSE_PER_CPU:
2903
+ raise RuntimeError(
2904
+ f"Peer {peer.name} is carrying unrelated load: {summary}.\n"
2905
+ "A measurement taken here would describe the contention, not the link.\n"
2906
+ "Wait for the machine to go quiet, measure on another pair, or — if this "
2907
+ "load is yours and expected — pass --skip-conflict-check."
2908
+ )
2909
+ if ratio >= _LOAD_WARN_PER_CPU:
2910
+ print(f"! {peer.name}: {summary} — comparisons survive if runs are interleaved, absolute latencies do not")
2911
+ else:
2912
+ print(f"+ {peer.name}: {summary}")
2913
+
2914
+
2823
2915
  def _ota_preflight(
2824
2916
  plan: OtaSmokePlan,
2825
2917
  *,
@@ -2924,6 +3016,7 @@ def _ota_preflight(
2924
3016
 
2925
3017
  if check_conflicts:
2926
3018
  _ota_conflict_check(plan, dry_run=dry_run)
3019
+ _ota_load_check(plan, dry_run=dry_run)
2927
3020
 
2928
3021
 
2929
3022
  _OTA_STAGE_EXCLUDES = {
@@ -3137,6 +3230,7 @@ def _ota_verify_checkouts(plan: OtaSmokePlan, *, dry_run: bool) -> None:
3137
3230
  distinct one-line failure here, before any container starts, rather than a
3138
3231
  confusing one an hour later.
3139
3232
  """
3233
+ peer_profiles: dict[str, list[str]] = {}
3140
3234
  for peer in plan.peers.values():
3141
3235
  checkout = shlex.quote(_ota_peer_workdir(plan, peer))
3142
3236
  project = shlex.quote(plan.project)
@@ -3174,6 +3268,46 @@ def _ota_verify_checkouts(plan: OtaSmokePlan, *, dry_run: bool) -> None:
3174
3268
  # a real hazard, but it is also a legitimate mid-migration state,
3175
3269
  # and a check that refuses it would be the wrong place to find out.
3176
3270
  print(f"+ {peer.name}: rosotacom {version.stdout.strip() or 'unknown'}")
3271
+ profiles = _ota_run(
3272
+ peer,
3273
+ f"{OTA_CHECKOUT_PATH_PREFIX}"
3274
+ 'ls "$(rosotacom resources path ws)"/ota_configs/*.template '
3275
+ "2>/dev/null | xargs -r -n1 basename | sort | tr '\\n' ' '",
3276
+ label=f"{peer.name}: OTA profile templates",
3277
+ dry_run=dry_run,
3278
+ batch=True,
3279
+ check=False,
3280
+ )
3281
+ if not dry_run and profiles.returncode == 0:
3282
+ peer_profiles[peer.name] = profiles.stdout.split()
3283
+
3284
+ # Unlike the version, this one refuses. A peer whose installed stack lacks
3285
+ # the template a session names does not fail loudly: the renderer errors,
3286
+ # the profile file is left unusable, and the DDS stack silently falls back
3287
+ # to its own defaults — multicast discovery, which no tunnelled OTA link
3288
+ # carries. The run then completes and reports zero delivered messages, which
3289
+ # reads as a middleware that does not work. That is exactly what happened on
3290
+ # 2026-08-19, when #299 made fastdds_tuned.xml the Fast DDS default while
3291
+ # the deployed stack predated the template: eight runs, all recorded as
3292
+ # evidence about Fast DDS, none of which had ever configured it.
3293
+ if len(peer_profiles) > 1:
3294
+ names = sorted(peer_profiles)
3295
+ reference = set(peer_profiles[names[0]])
3296
+ for name in names[1:]:
3297
+ missing = reference - set(peer_profiles[name])
3298
+ extra = set(peer_profiles[name]) - reference
3299
+ if missing or extra:
3300
+ detail = []
3301
+ if missing:
3302
+ detail.append(f"absent on {name}: {', '.join(sorted(missing))}")
3303
+ if extra:
3304
+ detail.append(f"only on {name}: {', '.join(sorted(extra))}")
3305
+ raise RuntimeError(
3306
+ f"Peers disagree about their OTA profile templates ({'; '.join(detail)}). "
3307
+ "A missing template renders nothing and the DDS stack falls back to "
3308
+ "defaults, so the link would come up and carry no data. Bring the peers "
3309
+ "to the same rosotacom before measuring."
3310
+ )
3177
3311
 
3178
3312
 
3179
3313
  def _ota_prepare_hosts(args: argparse.Namespace, runtime: RuntimeConfig, plan: OtaSmokePlan) -> None:
@@ -4159,6 +4293,7 @@ def _start_interactive_ota_smoke(args: argparse.Namespace) -> int:
4159
4293
 
4160
4294
  if not getattr(args, "skip_preflight", False) and not getattr(args, "skip_conflict_check", False):
4161
4295
  _ota_conflict_check(plan, dry_run=dry_run)
4296
+ _ota_load_check(plan, dry_run=dry_run)
4162
4297
  _ota_prepare_hosts(args, runtime, plan)
4163
4298
  instance = _resolve_session_instance(
4164
4299
  runtime,
@@ -4355,7 +4490,8 @@ def ota_smoke(args: argparse.Namespace) -> int:
4355
4490
  "session). Run the non-interactive ota-smoke to shape the link, or apply the profile manually."
4356
4491
  )
4357
4492
  return _start_interactive_ota_smoke(args)
4358
- return _start_noninteractive_ota_smoke(args)
4493
+ with _terminate_via_exception():
4494
+ return _start_noninteractive_ota_smoke(args)
4359
4495
 
4360
4496
 
4361
4497
  def _scenario_name_completer(
@@ -0,0 +1,80 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ Local-domain Fast DDS configuration: pin the loopback locators, change nothing else.
4
+
5
+ This is the Fast DDS counterpart to cyclonedds_local_participants.xml, and it
6
+ exists because the two stacks fail differently when nothing is configured.
7
+ CycloneDDS talks between containers on a host out of the box. Fast DDS does
8
+ not, and the way it fails is quiet: the publisher reports a matched
9
+ subscriber, the subscriber reports zero messages, and the session's status
10
+ shows the last stage IDLE with `pub=1` next to it — a pipeline that looks
11
+ connected and moves nothing.
12
+
13
+ Measured on lamborghini, 2026-08-19, two containers on `--network host`,
14
+ 84 B at 10 Hz, ~80 samples per run:
15
+
16
+ no profile at all .................................. 0 of 80
17
+ + shared memory off (useBuiltinTransports=false) ... 0 of 80
18
+ + a unicast initial peer on 127.0.0.1 .............. 0 of 80
19
+ + explicit unicast locator lists ................... 79 of 80
20
+ locator lists ALONE, shared memory left on ......... 79 of 80
21
+ (control) CycloneDDS, no profile ................... 79 of 80
22
+
23
+ So the locator lists are the whole fix, and shared memory is a red herring —
24
+ worth stating because turning SHM off is the obvious first guess and it
25
+ changes nothing here.
26
+
27
+ Why they are load-bearing: with no locator list Fast DDS binds every interface
28
+ it can enumerate and announces all of them. A developer machine running Docker
29
+ has dozens of `br-*` bridges, most of them DOWN, and a peer that walks that
30
+ list can settle on one that carries nothing. Naming 127.0.0.1 makes the
31
+ announcement match the one path that is certain to work between processes on
32
+ a shared network namespace.
33
+
34
+ Scope, and its one limit: this pins the LOCAL domain, which is host-local by
35
+ definition, so loopback is enough — and it is more robust than the host's
36
+ routable address, which depends on which interface is up. It assumes the
37
+ session's containers share a network namespace (`--network host`, which the
38
+ ros2docker profiles set). A container on its own bridge network has its own
39
+ 127.0.0.1 and would need its address named here instead.
40
+
41
+ Nothing else is set: no interface allowlist, no transport override, no
42
+ discovery tuning. Unlike the OTA templates this profile is not trying to cross
43
+ a tunnel.
44
+ -->
45
+ <dds xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
46
+ <profiles>
47
+ <participant profile_name="rosotacom_fastdds_local" is_default_profile="true">
48
+ <rtps>
49
+ <defaultUnicastLocatorList>
50
+ <locator>
51
+ <udpv4>
52
+ <address>127.0.0.1</address>
53
+ <port>0</port>
54
+ </udpv4>
55
+ </locator>
56
+ </defaultUnicastLocatorList>
57
+
58
+ <builtin>
59
+ <metatrafficUnicastLocatorList>
60
+ <locator>
61
+ <udpv4>
62
+ <address>127.0.0.1</address>
63
+ <port>0</port>
64
+ </udpv4>
65
+ </locator>
66
+ </metatrafficUnicastLocatorList>
67
+
68
+ <initialPeersList>
69
+ <locator>
70
+ <udpv4>
71
+ <address>127.0.0.1</address>
72
+ <port>0</port>
73
+ </udpv4>
74
+ </locator>
75
+ </initialPeersList>
76
+ </builtin>
77
+ </rtps>
78
+ </participant>
79
+ </profiles>
80
+ </dds>
@@ -12,6 +12,8 @@ get_fastdds_easy_mode_xml.py, get_cyclonedds_xml.py).
12
12
  import argparse
13
13
  import os
14
14
  import re
15
+ import sys
16
+ import tempfile
15
17
 
16
18
 
17
19
  KNOWN_PLACEHOLDERS = {"#host_ip", "#peer", "#easy_mode_ip", "#spdp_interval"}
@@ -204,6 +206,16 @@ if __name__ == "__main__":
204
206
  "-e", "--easy-mode-ip", dest="easy_mode_ip",
205
207
  help="Resolved Fast DDS Easy Mode address (#easy_mode_ip).",
206
208
  )
209
+ parser.add_argument(
210
+ "-o", "--output", dest="output",
211
+ help=(
212
+ "Write the rendered XML to this path, atomically. Prefer this over a shell "
213
+ "redirect: `> file` truncates the target before this script runs, so a "
214
+ "failure here leaves a zero-byte profile behind, and a zero-byte profile is "
215
+ "not an error to a DDS stack — it falls back to its defaults and the link "
216
+ "comes up carrying nothing."
217
+ ),
218
+ )
207
219
  parser.add_argument(
208
220
  "--spdp-interval",
209
221
  dest="spdp_interval",
@@ -211,4 +223,25 @@ if __name__ == "__main__":
211
223
  help=f"CycloneDDS SPDP interval duration (#spdp_interval, default: {DEFAULT_SPDP_INTERVAL}).",
212
224
  )
213
225
  args = parser.parse_args()
214
- print(main(**{k: v for k, v in vars(args).items() if v is not None}))
226
+ output = args.output
227
+ rendered = main(**{k: v for k, v in vars(args).items()
228
+ if v is not None and k != "output"})
229
+ if not output:
230
+ print(rendered)
231
+ raise SystemExit(0)
232
+ if not rendered.strip():
233
+ raise SystemExit(f"Refusing to write an empty profile to {output}.")
234
+ directory = os.path.dirname(os.path.abspath(output)) or "."
235
+ os.makedirs(directory, exist_ok=True)
236
+ handle, temporary = tempfile.mkstemp(dir=directory, suffix=".partial")
237
+ try:
238
+ with os.fdopen(handle, "w", encoding="utf-8") as stream:
239
+ stream.write(rendered)
240
+ os.replace(temporary, output)
241
+ except BaseException:
242
+ try:
243
+ os.unlink(temporary)
244
+ except OSError:
245
+ pass
246
+ raise
247
+ print(f"Wrote {output} ({len(rendered)} bytes).", file=sys.stderr)
@@ -292,7 +292,7 @@ common:
292
292
  before_commands:
293
293
  - if [ '${local_domain_id}' != "None" ] && [ -n '${local_domain_id}' ]; then export ROS_DOMAIN_ID='${local_domain_id}'; fi
294
294
  - if [ '${rmw_local}' != "None" ]; then case ${rmw_local} in cyclone) export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp;; fastdds) export RMW_IMPLEMENTATION=rmw_fastrtps_cpp; export FASTDDS_BUILTIN_TRANSPORTS=${FASTDDS_BUILTIN_TRANSPORTS:-UDPv4};; zenoh) export RMW_IMPLEMENTATION=rmw_zenoh_cpp;; *) export RMW_IMPLEMENTATION='${rmw_local}';; esac; fi
295
- - if [ '${local_config_template}' != "None" ] && [ -n '${local_config_template}' ]; then local_args=(--config "${local_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_easy_mode_ip}' != "None" ] && [ -n '${local_easy_mode_ip}' ] && local_args+=(--easy-mode-ip "${local_easy_mode_ip}"); [ '${local_spdp_interval}' != "None" ] && [ -n '${local_spdp_interval}' ] && local_args+=(--spdp-interval "${local_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${local_args[@]}" > "${local_config_file}"; case ${rmw_local} in cyclone) export CYCLONEDDS_URI="file://${local_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${local_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
295
+ - if [ '${local_config_template}' != "None" ] && [ -n '${local_config_template}' ]; then local_args=(--config "${local_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${local_easy_mode_ip}' != "None" ] && [ -n '${local_easy_mode_ip}' ] && local_args+=(--easy-mode-ip "${local_easy_mode_ip}"); [ '${local_spdp_interval}' != "None" ] && [ -n '${local_spdp_interval}' ] && local_args+=(--spdp-interval "${local_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${local_args[@]}" --output "${local_config_file}" || { echo "FATAL - could not render the local DDS profile ${local_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_local} in cyclone) export CYCLONEDDS_URI="file://${local_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${local_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
296
296
  - if [ '${before_command}' != "None" ]; then ${before_command}; fi
297
297
 
298
298
  windows:
@@ -338,7 +338,7 @@ windows:
338
338
  if: use_domain_bridge
339
339
  splits:
340
340
  - commands:
341
- - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" > "${ota_config_file}"; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
341
+ - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
342
342
  - ros2 run domain_bridge domain_bridge --wait-for-publisher false ${domain_bridge_config_file}
343
343
  - name: IN
344
344
  if: in
@@ -346,7 +346,7 @@ windows:
346
346
  - commands:
347
347
  - if [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ]; then export ROS_DOMAIN_ID='${ota_domain_id}'; fi
348
348
  - case ${rmw_ota} in cyclone) export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp;; fastdds) export RMW_IMPLEMENTATION=rmw_fastrtps_cpp; export FASTDDS_BUILTIN_TRANSPORTS=${FASTDDS_BUILTIN_TRANSPORTS:-UDPv4};; zenoh) export RMW_IMPLEMENTATION=rmw_zenoh_cpp;; esac
349
- - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" > "${ota_config_file}"; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
349
+ - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
350
350
  # For zenoh_ros2dds the OTA domain is loopback-only (zenoh is the inter-host
351
351
  # transport), so confine discovery to localhost instead of statically peering
352
352
  # the remote host — otherwise the OTA-domain DDS double-delivers alongside zenoh.
@@ -373,7 +373,7 @@ windows:
373
373
  - commands:
374
374
  - if [ '${ota_domain_id}' != "None" ] && [ -n '${ota_domain_id}' ]; then export ROS_DOMAIN_ID='${ota_domain_id}'; fi
375
375
  - case ${rmw_ota} in cyclone) export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp;; fastdds) export RMW_IMPLEMENTATION=rmw_fastrtps_cpp; export FASTDDS_BUILTIN_TRANSPORTS=${FASTDDS_BUILTIN_TRANSPORTS:-UDPv4};; zenoh) export RMW_IMPLEMENTATION=rmw_zenoh_cpp;; esac
376
- - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" > "${ota_config_file}"; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
376
+ - if [ '${ota_config_template}' != "None" ] && [ -n '${ota_config_template}' ]; then ota_args=(--config "${ota_config_template}" --host-ip "${ip_local}" --peer "${ip_remote}"); [ '${ota_easy_mode_ip}' != "None" ] && [ -n '${ota_easy_mode_ip}' ] && ota_args+=(--easy-mode-ip "${ota_easy_mode_ip}"); [ '${ota_spdp_interval}' != "None" ] && [ -n '${ota_spdp_interval}' ] && ota_args+=(--spdp-interval "${ota_spdp_interval}"); python3 /ws/ota_configs/get_ota_xml.py "${ota_args[@]}" --output "${ota_config_file}" || { echo "FATAL - could not render the OTA DDS profile ${ota_config_template}; refusing to start on defaults." >&2; exit 1; }; case ${rmw_ota} in cyclone) export CYCLONEDDS_URI="file://${ota_config_file}";; fastdds) export FASTDDS_DEFAULT_PROFILES_FILE="${ota_config_file}"; export RMW_FASTRTPS_USE_QOS_FROM_XML=1;; esac; fi
377
377
  # For zenoh_ros2dds the OTA domain is loopback-only (zenoh is the inter-host
378
378
  # transport), so confine discovery to localhost instead of statically peering
379
379
  # the remote host — otherwise the OTA-domain DDS double-delivers alongside zenoh.
@@ -231,6 +231,24 @@ _DDS_OTA_DEFAULT_CONFIG = {
231
231
  "fastdds": "fastdds_tuned.xml",
232
232
  }
233
233
 
234
+ # The *local* domain needs a default only for Fast DDS, and only because the two
235
+ # stacks differ in what they do with no configuration at all. CycloneDDS talks
236
+ # between containers on a host out of the box; Fast DDS announces every
237
+ # interface it can enumerate, a developer machine has dozens of down `br-*`
238
+ # bridges, and the peer can settle on one that carries nothing. Measured
239
+ # 2026-08-19 on two containers sharing a network namespace: 0 of 80 samples
240
+ # without a profile, 79 of 80 once the loopback locators are named. The failure
241
+ # is quiet — the writer reports a matched reader and the pipeline's last stage
242
+ # sits IDLE with pub=1 — so this is a default rather than something a session
243
+ # has to know to ask for.
244
+ #
245
+ # Cyclone deliberately has no entry: cyclonedds_local_participants.xml raises
246
+ # the participant-index ceiling, which is a control-centre concern, not
247
+ # something every session should inherit.
248
+ _DDS_LOCAL_DEFAULT_CONFIG = {
249
+ "fastdds": "fastdds_local.xml",
250
+ }
251
+
234
252
 
235
253
  def _validate_positive_seconds(value: str, ctx: str) -> None:
236
254
  text = value.strip().lower()
@@ -2207,8 +2225,9 @@ def func(
2207
2225
  items: List[Tuple[str, Any]] = []
2208
2226
  if effective_impl is not None:
2209
2227
  items.append(("rmw_local", effective_impl))
2210
- if side.dds_config:
2211
- items.append(("local_config_template", side.dds_config))
2228
+ local_dds_config = side.dds_config or _DDS_LOCAL_DEFAULT_CONFIG.get(effective_impl or "")
2229
+ if local_dds_config:
2230
+ items.append(("local_config_template", local_dds_config))
2212
2231
  items.append(("local_config_file", "${peer_dir}/local_dds.xml"))
2213
2232
  if side.impl == "fastdds" and side.dds_config == "fastdds_easy_mode.xml":
2214
2233
  items.append(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev55
3
+ Version: 2.5.dev56
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -207,6 +207,7 @@ src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.templat
207
207
  src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template
208
208
  src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template
209
209
  src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template
210
+ src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template
210
211
  src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template
211
212
  src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template
212
213
  src/rosotacom/resources/ws/ota_configs/get_ota_xml.py
@@ -210,6 +210,7 @@
210
210
  "src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template",
211
211
  "src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template",
212
212
  "src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template",
213
+ "src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template",
213
214
  "src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template",
214
215
  "src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template",
215
216
  "src/rosotacom/resources/ws/ota_configs/get_ota_xml.py",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "2.4",
3
- "distance": 55,
4
- "node": "gd772be38947b2d3b6d4fc5d45d9fa57da1e1723a",
3
+ "distance": 56,
4
+ "node": "g18a62c4bee965c3d64b7eaa70544f6bb7f078a98",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-08-19"
@@ -437,3 +437,42 @@ def test_no_credential_path_is_tracked_and_no_symlink_escapes_the_repository() -
437
437
  f"tracked symlinks point outside the repository: {escaping}. Checking one out overwrites "
438
438
  "whatever is at that path, and it is shipped in the sdist."
439
439
  )
440
+
441
+
442
+ def test_dds_profiles_are_rendered_atomically_and_never_left_empty() -> None:
443
+ """A profile that fails to render must not leave a usable-looking file.
444
+
445
+ `get_ota_xml.py ... > file` truncates the target before the renderer runs, so
446
+ a missing template leaves a zero-byte profile behind — and a zero-byte
447
+ profile is not an error to either DDS stack. Fast DDS logs a parse error and
448
+ falls back to multicast discovery, which a tunnelled OTA link cannot carry,
449
+ so the session comes up healthy-looking and delivers nothing. That is how
450
+ eight runs on 2026-08-19 were recorded as "Fast DDS delivers 0 messages"
451
+ when the middleware had simply never been configured.
452
+ """
453
+ plugin_path = cli.WS_DIR / "session" / "content" / "base" / "session_plugin_base.yaml"
454
+ text = plugin_path.read_text(encoding="utf-8")
455
+
456
+ # Not [^;]* — the failure branch itself contains a ';', and stopping there
457
+ # would silently check only the half of the statement that cannot fail.
458
+ calls = [text[m.start() : m.start() + 400] for m in re.finditer(r"get_ota_xml\.py", text)]
459
+ assert calls, "no get_ota_xml.py call sites found — did the plugin move?"
460
+ for call in calls:
461
+ assert "--output" in call, f"renders through a shell redirect: {call[:120]}"
462
+ assert not re.search(r">\s*\"\$\{\w*config_file\}\"", call), f"still truncates its target: {call[:120]}"
463
+ assert "exit 1" in call, f"a failed render does not stop the start: {call[:120]}"
464
+
465
+
466
+ def test_every_default_ota_profile_template_is_packaged() -> None:
467
+ """The default a session falls back to must exist in the installed package.
468
+
469
+ #299 pointed the Fast DDS default at fastdds_tuned.xml while the stack
470
+ deployed on the bench machines predated that template, so every Fast DDS run
471
+ rendered nothing.
472
+ """
473
+ from rosotacom.resources.ws.session.creation import generate_session_files as gsf
474
+
475
+ ota_configs = cli.WS_DIR / "ota_configs"
476
+ defaults = list(gsf._DDS_OTA_DEFAULT_CONFIG.values()) + list(gsf._DDS_LOCAL_DEFAULT_CONFIG.values())
477
+ missing = [name for name in defaults if name and not (ota_configs / f"{name}.template").exists()]
478
+ assert not missing, f"default profile templates absent from the package: {missing}"
@@ -8,6 +8,8 @@ parallel/conflict end-to-end half lives in tests/e2e/test_parallel_smoke.py.
8
8
  from __future__ import annotations
9
9
 
10
10
  import argparse
11
+ import os
12
+ import signal
11
13
  import subprocess
12
14
  from pathlib import Path
13
15
 
@@ -604,3 +606,99 @@ def test_local_benchmark_aborts_on_any_active_rosotacom_container(
604
606
  def test_local_benchmark_passes_on_quiet_host(monkeypatch: pytest.MonkeyPatch) -> None:
605
607
  monkeypatch.setattr(rosotacom, "_list_docker_containers", lambda all_states=False: [("onedrive", ["bridge"])])
606
608
  cli_benchmark._abort_on_local_benchmark_conflicts(argparse.Namespace())
609
+
610
+
611
+ def _load_plan(tmp_path: Path) -> rosotacom.OtaSmokePlan:
612
+ return rosotacom.OtaSmokePlan(
613
+ state_path=tmp_path / "ota-deployment.yaml",
614
+ workdir="/tmp/rosotacom_ota",
615
+ rosotacom="rosotacom",
616
+ project="rosotacom.yaml",
617
+ peers={"a": rosotacom.OtaSmokePeer("a", None, "10.0.0.10")},
618
+ )
619
+
620
+
621
+ def test_ota_load_check_refuses_a_peer_someone_else_is_saturating(
622
+ tmp_path: Path,
623
+ monkeypatch: pytest.MonkeyPatch,
624
+ ) -> None:
625
+ """A busy peer must stop the run, not quietly distort it.
626
+
627
+ Measured on majestic on 2026-08-19: load 78 on 32 CPUs while a colleague
628
+ trained models. Runs there did not fail — they completed and reported
629
+ numbers that described the contention.
630
+ """
631
+ monkeypatch.setattr(
632
+ rosotacom,
633
+ "_ota_run",
634
+ _fake_ota_run_factory({"/proc/loadavg": "78.24 81.86 81.28\n32\n"}),
635
+ )
636
+
637
+ with pytest.raises(RuntimeError) as excinfo:
638
+ rosotacom._ota_load_check(_load_plan(tmp_path), dry_run=False)
639
+
640
+ message = str(excinfo.value)
641
+ assert "unrelated load" in message
642
+ assert "--skip-conflict-check" in message
643
+
644
+
645
+ def test_ota_load_check_warns_but_proceeds_on_moderate_load(
646
+ tmp_path: Path,
647
+ monkeypatch: pytest.MonkeyPatch,
648
+ capsys: pytest.CaptureFixture[str],
649
+ ) -> None:
650
+ monkeypatch.setattr(
651
+ rosotacom,
652
+ "_ota_run",
653
+ _fake_ota_run_factory({"/proc/loadavg": "9.0 8.0 7.0\n8\n"}),
654
+ )
655
+ rosotacom._ota_load_check(_load_plan(tmp_path), dry_run=False)
656
+ out = capsys.readouterr().out
657
+ assert "interleaved" in out
658
+
659
+
660
+ def test_ota_load_check_is_quiet_on_an_idle_peer(
661
+ tmp_path: Path,
662
+ monkeypatch: pytest.MonkeyPatch,
663
+ capsys: pytest.CaptureFixture[str],
664
+ ) -> None:
665
+ """The reading is printed even when it passes: a result should carry the
666
+ conditions it was measured under, not only the ones that stopped it."""
667
+ monkeypatch.setattr(
668
+ rosotacom,
669
+ "_ota_run",
670
+ _fake_ota_run_factory({"/proc/loadavg": "0.21 0.08 0.02\n8\n"}),
671
+ )
672
+ rosotacom._ota_load_check(_load_plan(tmp_path), dry_run=False)
673
+ out = capsys.readouterr().out
674
+ assert "0.21" in out and "8 CPUs" in out
675
+
676
+
677
+ def test_sigterm_unwinds_so_cleanup_runs() -> None:
678
+ """SIGTERM must reach the run's `finally`, not end the process outright.
679
+
680
+ `timeout`, systemd, and a cancelled CI step all send SIGTERM. Under the
681
+ default disposition Python exits immediately, so the OTA run's cleanup —
682
+ revert shaping, collect logs, stop both peers — never happens. On
683
+ 2026-08-19 a run killed by `timeout` left containers on both peers, which
684
+ then blocked the next run's exclusivity check.
685
+ """
686
+ cleaned: list[str] = []
687
+
688
+ with pytest.raises(SystemExit) as excinfo:
689
+ with rosotacom._terminate_via_exception():
690
+ try:
691
+ os.kill(os.getpid(), signal.SIGTERM)
692
+ finally:
693
+ cleaned.append("teardown ran")
694
+
695
+ assert cleaned == ["teardown ran"]
696
+ assert excinfo.value.code == 128 + signal.SIGTERM
697
+
698
+
699
+ def test_sigterm_disposition_is_restored() -> None:
700
+ """The handler must not leak into whatever called us."""
701
+ before = signal.getsignal(signal.SIGTERM)
702
+ with rosotacom._terminate_via_exception():
703
+ assert signal.getsignal(signal.SIGTERM) is not before
704
+ assert signal.getsignal(signal.SIGTERM) is before