rosotacom-dev 2.5.dev58__tar.gz → 2.5.dev60__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 (382) hide show
  1. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/README.md +2 -0
  3. rosotacom_dev-2.5.dev60/docs/findings/a-transport-element-doubles-every-fastdds-sample.md +117 -0
  4. rosotacom_dev-2.5.dev60/docs/findings/lifespan-is-enforced-by-one-reader-and-not-the-other.md +108 -0
  5. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/cli.py +61 -1
  6. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +40 -42
  7. rosotacom_dev-2.5.dev60/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +93 -0
  8. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  9. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/SOURCES.txt +2 -0
  10. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/scm_file_list.json +2 -0
  11. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  12. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_concurrency.py +94 -0
  13. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_ota_link_configs.py +35 -4
  14. rosotacom_dev-2.5.dev58/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -71
  15. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.devcontainer/devcontainer.json.template +0 -0
  16. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  17. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/CODEOWNERS +0 -0
  18. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  19. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  20. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  21. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  22. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  23. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  24. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  25. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  26. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/actions/setup-just/action.yml +0 -0
  28. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/dependabot.yml +0 -0
  29. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/benchmark-calibrate.yml +0 -0
  30. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/benchmark-gate.yml +0 -0
  31. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/dev-release.yml +0 -0
  32. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/image-scan.yml +0 -0
  33. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/nightly-e2e.yml +0 -0
  34. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/pr-lightweight.yml +0 -0
  35. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/pr-merge-gate.yml +0 -0
  36. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.github/workflows/release.yml +0 -0
  37. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/.pre-commit-config.yaml +0 -0
  38. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/AGENTS.md +0 -0
  39. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/CONCURRENCY.md +0 -0
  40. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/CONTRIBUTING.md +0 -0
  41. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/DEVELOPMENT_PRINCIPLES.md +0 -0
  42. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/LICENSE.txt +0 -0
  43. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/MANIFEST.in +0 -0
  44. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/README.md +0 -0
  45. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/budgets.jsonl +0 -0
  46. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/calibration/README.md +0 -0
  47. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/calibration/capacity-size-tight-cyclone.json +0 -0
  48. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  49. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/calibration/probe-loss-nominal-cyclone.json +0 -0
  50. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/calibration/probe-loss-tight-cyclone.json +0 -0
  51. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/calibration/probe-loss-tight-fastdds.json +0 -0
  52. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/calibration/probe-loss-tight-zenoh.json +0 -0
  53. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/codecov.yml +0 -0
  54. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/deployment-configuration.md +0 -0
  55. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/benchmark-ab.md +0 -0
  56. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/benchmark-bag-as-load.md +0 -0
  57. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/ci.md +0 -0
  58. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/ffmpeg-keyframes.md +0 -0
  59. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  60. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/delay-alone-no-loss.md +0 -0
  61. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
  62. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
  63. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/head-of-line-irregularity.md +0 -0
  64. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  65. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
  66. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/lighter-message-loss.md +0 -0
  67. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/oversubscription-queues-not-losses.md +0 -0
  68. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/reorder-becomes-reader-loss.md +0 -0
  69. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/repro/decode_chain_repro.py +0 -0
  70. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/repro/reorder_drop_repro.py +0 -0
  71. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
  72. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  73. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/forensics-report.md +0 -0
  74. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/geo-link-map.md +0 -0
  75. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/github-repository-settings.md +0 -0
  76. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/link-bandwidth.md +0 -0
  77. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/link-trace.md +0 -0
  78. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/owner-runbook.md +0 -0
  79. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/performance-bands.md +0 -0
  80. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/playbooks/README.md +0 -0
  81. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/quality-model.md +0 -0
  82. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/release-notes/TEMPLATE.md +0 -0
  83. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/release-notes/v2.1.md +0 -0
  84. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/release-notes/v2.2.md +0 -0
  85. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/release-notes/v2.3.md +0 -0
  86. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/release-notes/v2.4.md +0 -0
  87. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/release.md +0 -0
  88. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  89. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/0002-expectation-concepts.md +0 -0
  90. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/0003-metric-backbone.md +0 -0
  91. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/0004-network-profiles.md +0 -0
  92. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  93. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/0006-dynamic-qos.md +0 -0
  94. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/0007-regression-gate.md +0 -0
  95. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/rfcs/README.md +0 -0
  96. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/testing.md +0 -0
  97. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/docs/work-items.md +0 -0
  98. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/install.sh +0 -0
  99. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/justfile +0 -0
  100. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/media/control_center.png +0 -0
  101. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/media/echo_test.png +0 -0
  102. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/media/forensics-report-example.png +0 -0
  103. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/media/host.png +0 -0
  104. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/media/vehicle.png +0 -0
  105. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/pyproject.toml +0 -0
  106. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/requirements.txt +0 -0
  107. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/rosotacom.yaml +0 -0
  108. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/session-configuration.md +0 -0
  109. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/setup.cfg +0 -0
  110. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/__init__.py +0 -0
  111. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/__main__.py +0 -0
  112. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/anonymize.py +0 -0
  113. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/bag_ground_truth.py +0 -0
  114. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/benched_set.py +0 -0
  115. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/benchmark.py +0 -0
  116. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/benchmark_delta.py +0 -0
  117. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/bundle_check.py +0 -0
  118. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/cli_benchmark.py +0 -0
  119. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/deployment.py +0 -0
  120. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/ffmpeg_packet.py +0 -0
  121. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/forensics.py +0 -0
  122. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/geomap.py +0 -0
  123. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/image_cache.py +0 -0
  124. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/netem_dist.py +0 -0
  125. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/network_preflight.py +0 -0
  126. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/network_profiles.py +0 -0
  127. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/network_shaper.py +0 -0
  128. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/plots.py +0 -0
  129. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/py.typed +0 -0
  130. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/benched-set.yaml +0 -0
  131. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/.gitignore +0 -0
  132. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/README.md +0 -0
  133. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/__init__.py +0 -0
  134. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  135. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  136. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
  137. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/profiles.yaml +0 -0
  138. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  139. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  140. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  141. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  142. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  143. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  144. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  145. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  146. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  147. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  148. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  149. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  150. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  151. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  152. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  153. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  154. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  155. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  156. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  157. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  158. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  159. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  160. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  161. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  162. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  163. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  164. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  165. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  166. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  167. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  168. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  169. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  170. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  171. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  172. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  173. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  174. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  175. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  176. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  177. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  178. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  179. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  180. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  181. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  182. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  183. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  184. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  185. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  186. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  187. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  188. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  189. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  190. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  191. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  192. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  193. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  195. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  196. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  198. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  199. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  200. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  201. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  202. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  203. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  204. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  205. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  206. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  207. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  208. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  209. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ros2docker.json.example +0 -0
  210. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  211. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  212. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  213. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  214. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  215. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  216. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  217. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  218. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  219. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  220. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  221. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  222. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
  223. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template +0 -0
  224. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
  225. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  226. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  227. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  228. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  229. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  230. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  231. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  232. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  233. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  234. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  235. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  236. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  237. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  238. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  239. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  240. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  241. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  242. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  243. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  244. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  245. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  246. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  247. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
  248. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  249. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  250. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  251. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  252. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  253. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  254. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  255. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  256. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  257. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  258. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  259. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  260. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  261. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  262. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  263. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  264. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  265. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  266. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
  267. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
  268. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  269. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  270. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  271. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  272. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  273. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  274. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  275. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  276. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  277. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  278. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  279. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  280. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  281. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  282. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  283. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  284. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  285. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  286. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  287. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  288. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  289. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  290. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  291. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  292. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  293. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
  294. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  295. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  296. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  297. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  298. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  299. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
  300. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  301. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  302. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  303. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/status_eval.py +0 -0
  304. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/stream_stats.py +0 -0
  305. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/trace_profiles.py +0 -0
  306. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/transit.py +0 -0
  307. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom/video_quality.py +0 -0
  308. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  309. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  310. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  311. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  312. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/terminology.md +0 -0
  313. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_benched_set_registry.py +0 -0
  314. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_findings.py +0 -0
  315. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_generated_topics_have_a_producer.py +0 -0
  316. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_markdown_links.py +0 -0
  317. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_ota_source_bundle.py +0 -0
  318. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_package_metadata.py +0 -0
  319. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_packaged_resources.py +0 -0
  320. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  321. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_pytest_policy.py +0 -0
  322. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_readme_examples.py +0 -0
  323. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_security_maintenance_config.py +0 -0
  324. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_session_configuration_doc.py +0 -0
  325. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_session_template_substitution.py +0 -0
  326. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/contract/test_workflow_contracts.py +0 -0
  327. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/conftest.py +0 -0
  328. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_anonymize_e2e.py +0 -0
  329. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_benchmark_ab.py +0 -0
  330. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_benchmark_capacity.py +0 -0
  331. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_benchmark_replay.py +0 -0
  332. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_parallel_smoke.py +0 -0
  333. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_smoke.py +0 -0
  334. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_timeline_stepping.py +0 -0
  335. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/e2e/test_video_quality_e2e.py +0 -0
  336. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/sessions/generate_rmw_matrix.py +0 -0
  337. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  338. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  339. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  340. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  341. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
  342. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  343. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_anonymize.py +0 -0
  344. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_bag_ground_truth.py +0 -0
  345. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_benched_set.py +0 -0
  346. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_benchmark.py +0 -0
  347. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_benchmark_delta.py +0 -0
  348. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_bundle_check.py +0 -0
  349. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_catmux_pane_logging.py +0 -0
  350. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_cli.py +0 -0
  351. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_cli_benchmark.py +0 -0
  352. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_container_shaping.py +0 -0
  353. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_deployment.py +0 -0
  354. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_ffmpeg_flags.py +0 -0
  355. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_ffmpeg_packet.py +0 -0
  356. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_field_profiles.py +0 -0
  357. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_forensics.py +0 -0
  358. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_geomap.py +0 -0
  359. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_image_cache.py +0 -0
  360. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_link_bytes.py +0 -0
  361. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_link_trace.py +0 -0
  362. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_netem_dist.py +0 -0
  363. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_network_preflight.py +0 -0
  364. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_network_profiles.py +0 -0
  365. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_network_shaper.py +0 -0
  366. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_ota_unwrapper.py +0 -0
  367. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_ota_wrapper.py +0 -0
  368. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_ota_xml.py +0 -0
  369. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_playout_pacer.py +0 -0
  370. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_plots.py +0 -0
  371. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_pub_sub_pair_types.py +0 -0
  372. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_qos_fallback_logging.py +0 -0
  373. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_relay_optional_parameters.py +0 -0
  374. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_session_pipeline.py +0 -0
  375. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_stage_latency.py +0 -0
  376. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_status_eval.py +0 -0
  377. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_status_overview.py +0 -0
  378. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_status_overview_observer.py +0 -0
  379. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_stream_stats.py +0 -0
  380. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_trace_profiles.py +0 -0
  381. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/tests/unit/test_transit.py +0 -0
  382. {rosotacom_dev-2.5.dev58 → rosotacom_dev-2.5.dev60}/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.dev58
3
+ Version: 2.5.dev60
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -32,6 +32,8 @@ living verification hook.
32
32
  - [A bridge that learns types from the graph cannot use a transport that does not carry the graph](graph-derived-types-block-a-non-graph-transport.md) - over `zenoh_ros2dds` the payload topic never arrived in either run: the bridge routes a topic once a local reader exists and the reader waited for the bridge, leaving only `Pending=1` in a log.
33
33
  - [A Fast DDS datagram cap stops every message larger than it](fastdds-datagram-cap-drops-large-samples.md) - copying CycloneDDS's 1200 B fragment size into the Fast DDS OTA profile makes every sample above the cap never arrive, at 1200 B and at 8192 B, sync and async — while the 84 B heartbeat keeps flowing at 10 Hz.
34
34
  - [A lost keyframe silences exactly the next gop_size−2 deltas](keyframe-loss-kills-the-next-deltas.md) - libavcodec emits nothing for them, the last delta decodes with artifacts, the next keyframe restores clean decode; a lost delta never blocks decode — so the display hole is gop_size × frame period, deterministically.
35
+ - [Any transport element in a Fast DDS profile doubles every sample on the wire](a-transport-element-doubles-every-fastdds-sample.md) - 366 datagrams for 180 samples against 183 without it; `<builtinTransports>DEFAULT` is a no-op selection and doubles too, so the trigger is the element's presence — removing it takes the shipped OTA profile from 2.10x the payload to 1.04x at identical delivery.
36
+ - [Lifespan is enforced by one DDS reader and not the other](lifespan-is-enforced-by-one-reader-and-not-the-other.md) - under the same 20% oversubscription both stacks queue alike (472 and 433 delivered at ~1.1-1.2 s); with `lifespan: 0.7` CycloneDDS is unchanged and delivers 9.1% inside the bound, while Fast DDS drops to 4 delivered and never exceeds 570 ms — the policy decides shown-vs-suppressed, not how long the wait is.
35
37
  - [Oversubscription queues — it does not lose until the queue itself dies](oversubscription-queues-not-losses.md) - 16% over an emulated cap climbed one-way delay 340 → 3250 ms across twelve bins with zero loss in every one; the losses arrived only when the queue died, all at once.
36
38
  - [Best-effort reordering becomes reader-side loss, independent of history depth](reorder-becomes-reader-loss.md) - under pure delay jitter a Cyclone best-effort reader discards every overtaken sample (57% at 100 Hz under 50+-45 ms), delivery stays strictly monotonic, and depth 50 loses the same as depth 1.
37
39
  - [Jitter causes loss while bandwidth shortage builds latency](jitter-loss-bandwidth-latency.md) - 18 KB at 20 Hz separates jitter-induced loss from bandwidth-induced queueing.
@@ -0,0 +1,117 @@
1
+ # Any Transport Element In A Fast DDS Profile Doubles Every Sample On The Wire
2
+
3
+ ## Claim
4
+
5
+ A Fast DDS participant profile that carries **any** transport element transmits
6
+ every sample twice. It is not a property of the transport that gets selected:
7
+ `<builtinTransports>DEFAULT</builtinTransports>` asks for exactly the built-in
8
+ set — a semantic no-op — and doubles the wire just as a full custom
9
+ `<userTransports>` descriptor does. Removing the element is the entire fix.
10
+
11
+ Measured against a 960 kbit/s payload, on the profile this repository ships:
12
+
13
+ | participant profile carries | interface tx |
14
+ |---|---|
15
+ | `<userTransports>` + `<useBuiltinTransports>false` (as shipped) | 1741 kbit/s |
16
+ | `<builtinTransports non_blocking="true">UDPv4` | 1741 kbit/s |
17
+ | `<builtinTransports>UDPv4` | 1741 kbit/s |
18
+ | `<builtinTransports>DEFAULT` | 1741 kbit/s |
19
+ | **no transport element** | **1007 kbit/s** |
20
+
21
+ Counted per datagram: **366** to the reader's data locator for **180** published
22
+ samples, against **183** without the element — each carrying a full 11 951 B of
23
+ UDP payload, to the same address and the same port. The reader discards the
24
+ second copy by sequence number, so it is delivered exactly once and costs half
25
+ the link.
26
+
27
+ The consequence for any published wire-volume comparison is direct: a Fast DDS
28
+ row measured with such a profile is a measurement of this defect, not of the
29
+ middleware.
30
+
31
+ ## Setup
32
+
33
+ - Host pair / topology: one host, two containers on a private docker bridge
34
+ (172.31.77.0/24), publisher and subscriber in separate containers; and the
35
+ same profile on the host network of a multi-homed machine (38 interfaces:
36
+ docker bridges, a VPN tun, WLAN) for the discovery checks.
37
+ - Session: a two-process rclpy probe, not the session pipeline — one
38
+ `std_msgs/String` topic at 12000 B and 10 Hz, best_effort / KEEP_LAST depth 1,
39
+ publisher started after the subscriber. Bytes are read from
40
+ `/sys/class/net/eth0/statistics/tx_bytes` around the run; datagrams are counted
41
+ from a `tshark` capture with `ip.defragment:FALSE`, so the packet count and the
42
+ interface counter agree.
43
+ - rosotacom SHA: `fastdds_tuned.xml.template` as of this commit, rendered by
44
+ `get_ota_xml.py`. ROS 2 Kilted, Fast DDS 3.2.4, `rmw_fastrtps_cpp` 9.3.4,
45
+ image `ros-communication:latest`.
46
+ - Profile: no shaping for the wire-volume rows — the point is what the stack
47
+ offers, not what a link does with it.
48
+ - Seed policy: deterministic; one run per cell, and the cells that share a
49
+ configuration agree to the byte (4 996 526 B across three different descriptor
50
+ shapes), which is what makes single runs readable here.
51
+
52
+ ## Evidence
53
+
54
+ Evidence grade: interface counters plus a packet capture of the same runs, on
55
+ both a synthetic profile and the rendered shipped one.
56
+
57
+ The trigger is the element, not its contents. Four descriptor shapes, all 366
58
+ datagrams for 180 samples: a bare `<transport_descriptor>` with nothing but
59
+ `transport_id` and `type`; the same plus `maxInitialPeersRange`; the same with
60
+ `<useBuiltinTransports>` set to `true` instead of `false`; and the same
61
+ allowlisted to a single interface. The single-interface row also refutes the
62
+ obvious alternative explanation — that the participant sends one copy per bound
63
+ interface.
64
+
65
+ On the rendered `fastdds_tuned.xml`, correcting for the ~40 messages published
66
+ before discovery completes (a Fast DDS writer with no matched reader puts nothing
67
+ on the wire, and this profile takes 3.9–4.2 s to match):
68
+
69
+ | | interface tx | transmitted payload | ratio |
70
+ |---|---|---|---|
71
+ | as shipped | 6516 kB | 3109 kB | **2.10×** |
72
+ | with the element removed | 3270 kB | 3145 kB | **1.04×** |
73
+
74
+ Both delivered 245 of 245, at a sub-millisecond median age. Nothing is traded for
75
+ the halving. For scale, CycloneDDS on the same bench and payload costs 963 kbit/s
76
+ — **1.00×** — so removing the element brings Fast DDS from twice Cyclone's wire
77
+ need to within 4% of it.
78
+
79
+ Two properties the removed descriptor carried were checked rather than assumed,
80
+ on the multi-homed host the profile exists for:
81
+
82
+ - **The same-host path stays open.** 195 of 195 delivered, discovery in 4.13 s,
83
+ across 38 interfaces. The pinned `defaultUnicastLocatorList` /
84
+ `metatrafficUnicastLocatorList` already say what the interface allowlist said.
85
+ - **Discovery survives the lost `maxInitialPeersRange`.** With 12 extra
86
+ participants started first, so the probe processes take high participant
87
+ indices: 195 of 195, discovery in 4.24 s, unchanged.
88
+
89
+ What is genuinely given up: `<non_blocking_send>true</non_blocking_send>` goes
90
+ with the descriptor, so a full send buffer blocks the writer instead of dropping.
91
+ That is the smaller cost — a blocking send stalls only while the link is behind,
92
+ where the duplicate costs half the link at all times.
93
+
94
+ Verification: automated:
95
+ `pytest tests/unit/test_ota_link_configs.py::test_no_fastdds_profile_names_a_transport`
96
+ guards the deletion in every packaged Fast DDS profile. Manual, for the effect
97
+ itself: two containers on a private bridge, one 12 kB/10 Hz stream, read
98
+ `tx_bytes` around a 30 s run with and without a `<builtinTransports>DEFAULT</builtinTransports>`
99
+ line in the participant profile — the ratio must be 2:1.
100
+
101
+ ## Status
102
+
103
+ confirmed, 2026-08-19.
104
+
105
+ ## Publication notes
106
+
107
+ This is a Fast DDS 3.2.4 / `rmw_fastrtps_cpp` 9.3.4 behaviour and is worth
108
+ reporting upstream; the `DEFAULT` row is the reproducer to lead with, because it
109
+ removes every question about what the profile was asking for.
110
+
111
+ It retires the "Fast DDS's lower loss is bought with bandwidth" reading of this
112
+ project's wire-volume table: roughly half of the excess over CycloneDDS was this,
113
+ and the remainder is still unattributed. Pair with
114
+ [lifespan-is-enforced-by-one-reader-and-not-the-other.md](lifespan-is-enforced-by-one-reader-and-not-the-other.md)
115
+ — together they account for both columns on which Fast DDS looked like a
116
+ different kind of stack: it costs about what Cyclone costs, and it delivers less
117
+ because it refuses to deliver stale.
@@ -0,0 +1,108 @@
1
+ # Lifespan Is Enforced By One DDS Reader And Not The Other
2
+
3
+ ## Claim
4
+
5
+ Under identical oversubscription the two DDS stacks queue identically — 472 and
6
+ 433 delivered of ~490 offered, at medians of 1146 ms and 1220 ms. The stacks
7
+ diverge only when `lifespan` is switched on, and then completely: CycloneDDS is
8
+ unchanged (472 delivered, 1143 → 1146 ms, **9.1%** of samples inside the 0.7 s
9
+ bound they were published with), while Fast DDS drops from 433 delivered to
10
+ **4**, at a 344 ms median, and **never delivers a sample older than 570 ms**.
11
+
12
+ `rmw_cyclonedds` does not enforce lifespan at the reader; Fast DDS does. Neither
13
+ enforcement shortens the delay — the backlog is below DDS and unrecallable
14
+ either way. What lifespan decides is whether a late sample is **shown or
15
+ suppressed**, and for an operator picture those are different failures: 7.9 Hz of
16
+ video more than a second old, against 0.07 Hz of video that is current.
17
+
18
+ The corollary matters for reading any oversubscription result: **Fast DDS's
19
+ apparent collapse under a cap is not a transport failure.** With the policy off
20
+ it carries the same load as CycloneDDS. The missing messages are its reader
21
+ discarding what arrived too late.
22
+
23
+ ## Setup
24
+
25
+ - Host pair / topology: one host, two containers on a private docker bridge
26
+ (172.31.77.0/24), publisher and subscriber in separate containers. Single host
27
+ on purpose: both sides read the same `CLOCK_REALTIME`, so a sample's age is
28
+ measured without a clock-offset term.
29
+ - Session: a two-process rclpy probe, not the session pipeline — one
30
+ `std_msgs/String` topic at 12000 B and 10 Hz (960 kbit/s payload), QoS
31
+ best_effort / KEEP_LAST depth 1, publisher started after the subscriber so
32
+ discovery is not inside the measurement. The payload carries its own sequence
33
+ number and publish stamp.
34
+ - rosotacom SHA: not involved; ROS 2 Kilted, Fast DDS 3.2.4 /
35
+ `rmw_fastrtps_cpp` 9.3.4, CycloneDDS via `rmw_cyclonedds_cpp`, image
36
+ `ros-communication:latest`.
37
+ - Profile: `tc qdisc add dev eth0 root tbf rate 800kbit burst 64kb latency
38
+ 2000ms` on the publisher container's egress — 20% below the offered payload,
39
+ with a 2 s buffer, i.e. a deliberate standing queue. 60 s per run.
40
+ - Both middlewares configured with explicit unicast discovery (the bridge
41
+ carries unicast but not the DDS discovery multicast), so neither runs on
42
+ defaults.
43
+ - Seed policy: no stochastic netem elements (rate only); one run per cell,
44
+ reported as the four-cell A/B rather than as a distribution.
45
+
46
+ ## Evidence
47
+
48
+ Evidence grade: single-host container runs, per-sample age computed from a
49
+ publish stamp inside the payload against one clock, sequence numbers giving loss
50
+ independently of the age.
51
+
52
+ | middleware | lifespan | delivered | age p50 | age p95 | age max | inside 0.7 s |
53
+ |---|---|---|---|---|---|---|
54
+ | CycloneDDS | 0.7 s | 472 | 1146 ms | 1449 ms | 1533 ms | **9.1%** |
55
+ | CycloneDDS | 86400 s (off) | 472 | 1143 ms | 1449 ms | 1533 ms | — |
56
+ | Fast DDS | 0.7 s | **4** | **344 ms** | 570 ms | **570 ms** | **100%** |
57
+ | Fast DDS | 86400 s (off) | **433** | 1220 ms | 1459 ms | 1557 ms | — |
58
+
59
+ Three readings:
60
+
61
+ 1. **The two stacks queue the same.** With the policy off, 472 against 433 and
62
+ 1143 ms against 1220 ms. Interface accounting agrees: 811 and 812 kbit/s, both
63
+ pinned to the 800 kbit/s cap. Whatever separates them under `lifespan: 0.7` is
64
+ not capacity and not the transport.
65
+ 2. **CycloneDDS's A/B is a null result, and that is the finding.** 472/472,
66
+ 1146/1143 ms — turning the policy off changes nothing measurable, because the
67
+ policy was doing nothing. Only 9.1% of what it delivers is inside the bound it
68
+ was published with.
69
+ 3. **Fast DDS's A/B is the opposite.** 4 against 433, and the max age under the
70
+ policy is 570 ms — below 700 ms, not approximately below it. The samples that
71
+ vanish are the ones the shaper delayed past their lifespan.
72
+
73
+ The same pair of runs also shows the delay is a queueing discipline rather than a
74
+ property of the path: replacing `tbf` with `htb` + `fq_codel` at the same
75
+ 800 kbit/s takes CycloneDDS from 472 delivered at 1146 ms to 8 delivered at
76
+ **124 ms**, with 100% inside the lifespan. An AQM converts staleness into loss;
77
+ it does not create capacity.
78
+
79
+ Verification: manual: two containers on a private bridge, `tbf` as above on the
80
+ publisher's egress, one 12 kB/10 Hz best_effort depth-1 stream, run once with
81
+ `lifespan: 0.7` and once with it effectively off, for each RMW. The Cyclone pair
82
+ must be indistinguishable and the Fast DDS pair must differ by two orders of
83
+ magnitude in delivered count, with the `lifespan: 0.7` arm's maximum age below
84
+ the lifespan.
85
+
86
+ ## Status
87
+
88
+ confirmed, 2026-08-19.
89
+
90
+ ## Publication notes
91
+
92
+ This corrects, from the reader side, the conclusion drawn in the operator
93
+ harness's middleware comparison that lifespan is "indistinguishable for both DDS
94
+ stacks" — that A/B's Fast DDS arm ran against a zero-byte profile. It is the
95
+ enforcement half of
96
+ [oversubscription-queues-not-losses.md](oversubscription-queues-not-losses.md):
97
+ that finding shows the excess becomes delay rather than loss, this one shows what
98
+ each reader then does with the delayed sample.
99
+
100
+ For papers, the useful framing is that "loss" and "delivered" are not
101
+ middleware-comparable numbers unless the lifespan enforcement is stated with
102
+ them. A table that puts Fast DDS's 4 next to CycloneDDS's 472 without that column
103
+ reads as a transport difference and is not one.
104
+
105
+ Practical consequence: neither behaviour is usable at 20% oversubscription, so
106
+ this is a choice about how to fail, not a fix. The fix is below DDS (offer fewer
107
+ bytes than the link carries) or above it (receiver-side age gating that can skip
108
+ rather than render).
@@ -2912,6 +2912,66 @@ def _ota_load_check(plan: OtaSmokePlan, *, dry_run: bool) -> None:
2912
2912
  print(f"+ {peer.name}: {summary}")
2913
2913
 
2914
2914
 
2915
+ _REACH_ATTEMPTS = 3
2916
+ _REACH_BACKOFF_SECONDS = 2.0
2917
+ _REACH_TIMEOUT_SECONDS = 20.0
2918
+
2919
+
2920
+ def _ota_reach_check(peer: OtaSmokePeer, *, dry_run: bool) -> None:
2921
+ """Open the control channel to a peer, retrying a transport that flaked.
2922
+
2923
+ This is the only preflight step that retries, and the reason is the probe
2924
+ itself: the remote command is ``true``. It cannot fail. Every non-zero exit
2925
+ and every timeout here is therefore the transport — an ssh handshake that
2926
+ timed out because the peer was loaded, a link that had not come up yet —
2927
+ and never a condition on the machine that a second look would paper over.
2928
+
2929
+ The checks after this one deliberately do not retry. A container that is
2930
+ already running is still running a moment later, and a peer somebody else is
2931
+ saturating is still saturated; retrying those would convert a real refusal
2932
+ into a delayed one. Retrying is right where the answer is stateless and the
2933
+ failure is transient, and that is here alone.
2934
+
2935
+ Each attempt is printed. A run that needed three tries to reach a peer was
2936
+ taken under conditions worth knowing about, so the artefact says so rather
2937
+ than reporting a clean first-try connection.
2938
+ """
2939
+ if dry_run:
2940
+ _ota_run(peer, "true", label=f"{peer.name}: reachable", dry_run=True, batch=True)
2941
+ return
2942
+
2943
+ last_detail = ""
2944
+ for attempt in range(1, _REACH_ATTEMPTS + 1):
2945
+ suffix = "" if attempt == 1 else f" (attempt {attempt} of {_REACH_ATTEMPTS})"
2946
+ try:
2947
+ result = _ota_run(
2948
+ peer,
2949
+ "true",
2950
+ label=f"{peer.name}: reachable{suffix}",
2951
+ batch=True,
2952
+ check=False,
2953
+ timeout=_REACH_TIMEOUT_SECONDS,
2954
+ )
2955
+ except subprocess.TimeoutExpired:
2956
+ last_detail = f"no answer within {_REACH_TIMEOUT_SECONDS:.0f}s"
2957
+ else:
2958
+ if result.returncode == 0:
2959
+ if attempt > 1:
2960
+ print(
2961
+ f"! {peer.name}: reachable on attempt {attempt} of {_REACH_ATTEMPTS} — the transport was flaky"
2962
+ )
2963
+ return
2964
+ last_detail = ((result.stdout or "") + (result.stderr or "")).strip() or f"exit code {result.returncode}"
2965
+ if attempt < _REACH_ATTEMPTS:
2966
+ print(f"! {peer.name}: not reachable ({last_detail}); retrying")
2967
+ time.sleep(_REACH_BACKOFF_SECONDS * attempt)
2968
+
2969
+ raise RuntimeError(
2970
+ f"{peer.name}: not reachable via {_ota_peer_target(peer)} after "
2971
+ f"{_REACH_ATTEMPTS} attempts.\nLast failure: {last_detail}"
2972
+ )
2973
+
2974
+
2915
2975
  def _ota_preflight(
2916
2976
  plan: OtaSmokePlan,
2917
2977
  *,
@@ -2928,7 +2988,7 @@ def _ota_preflight(
2928
2988
  sudo_passwords = sudo_passwords or {}
2929
2989
  for peer in plan.peers.values():
2930
2990
  if _ota_peer_is_remote(peer):
2931
- _ota_run(peer, "true", label=f"{peer.name}: reachable", dry_run=dry_run, batch=True)
2991
+ _ota_reach_check(peer, dry_run=dry_run)
2932
2992
  required = ["python3", "docker", "tar"]
2933
2993
  if require_tmux:
2934
2994
  required.append("tmux")
@@ -26,44 +26,6 @@
26
26
  -->
27
27
  <dds xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
28
28
  <profiles>
29
- <transport_descriptors>
30
- <transport_descriptor>
31
- <transport_id>rosotacom_ota_udp</transport_id>
32
- <type>UDPv4</type>
33
-
34
- <sendBufferSize>1048576</sendBufferSize>
35
- <receiveBufferSize>4194304</receiveBufferSize>
36
-
37
- <!-- Drop rather than block when the socket buffer is full: on a shaped
38
- uplink the blocking default turns a link-layer backlog into
39
- application-level latency. -->
40
- <non_blocking_send>true</non_blocking_send>
41
-
42
- <!-- An initial peer given without a port makes Fast DDS probe
43
- participant indices 0..range on that address. The default is 4, and
44
- an OTA domain carrying a domain bridge plus the IN and OUT splits
45
- can hand out a higher index, so whichever process starts late is
46
- never found. CycloneDDS probes 10; this matches it. -->
47
- <maxInitialPeersRange>10</maxInitialPeersRange>
48
-
49
- <!-- Two addresses, both load-bearing. #host_ip is the OTA address, and
50
- pinning it is what keeps a multi-homed VPN host from announcing a
51
- locator the peer cannot route to. 127.0.0.1 is the same-host half:
52
- with a split OTA domain the stock `domain_bridge` process sits on
53
- that domain too and speaks whatever RMW the local side uses, so
54
- this participant has to be able to discover a peer on its own
55
- machine. An allowlist of the OTA address alone takes the loopback
56
- path away and the bridge and the OTA processes never see each
57
- other — a failure that reads as a dead link and is not one. -->
58
- <interfaces>
59
- <allowlist>
60
- <interface name="127.0.0.1" netmask_filter="OFF"/>
61
- <interface name="#host_ip" netmask_filter="OFF"/>
62
- </allowlist>
63
- </interfaces>
64
- </transport_descriptor>
65
- </transport_descriptors>
66
-
67
29
  <participant profile_name="rosotacom_fastdds_tuned" is_default_profile="true">
68
30
  <rtps>
69
31
  <defaultUnicastLocatorList>
@@ -128,10 +90,46 @@
128
90
  </initialPeersList>
129
91
  </builtin>
130
92
 
131
- <userTransports>
132
- <transport_id>rosotacom_ota_udp</transport_id>
133
- </userTransports>
134
- <useBuiltinTransports>false</useBuiltinTransports>
93
+ <!-- No transport element here, deliberately, and this is load-bearing.
94
+
95
+ Measured on a two-container bench (2026-08-19, ROS 2 Kilted, Fast
96
+ DDS 3.2.4, rmw_fastrtps_cpp 9.3.4): a participant profile that
97
+ carries *any* transport element transmits every sample twice.
98
+
99
+ <userTransports> + <useBuiltinTransports>false 1741 kbit/s
100
+ <builtinTransports non_blocking="true">UDPv4 1741 kbit/s
101
+ <builtinTransports>UDPv4 1741 kbit/s
102
+ <builtinTransports>DEFAULT 1741 kbit/s
103
+ no transport element at all 1007 kbit/s
104
+
105
+ against a 960 kbit/s payload. The DEFAULT row is what settles it:
106
+ that selection is a no-op — it asks for exactly the built-in set —
107
+ and it still doubles. Counted per datagram, 366 to the reader's data
108
+ locator for 180 published samples against 183 without the element,
109
+ each carrying a full 11 951 B of UDP payload to the same address and
110
+ port. The reader discards the second copy by sequence number, so it
111
+ buys nothing and costs half the link.
112
+
113
+ What removing it gives up, named rather than hidden:
114
+ * shared memory is enabled again. It is unused between machines,
115
+ and between processes on one host it is faster than the loopback
116
+ path this profile also names. fastdds_local.xml records that SHM
117
+ was measured and is not the cause of the local-domain failure.
118
+ * <non_blocking_send>true</non_blocking_send> is gone with the
119
+ descriptor, so a full send buffer blocks instead of dropping.
120
+ That is the smaller cost: a blocking send stalls the writer for
121
+ as long as the link is behind, where the duplicate costs half the
122
+ link all the time.
123
+ * maxInitialPeersRange falls back to 4 from 10, and the interface
124
+ allowlist is gone. The locator lists above already pin what this
125
+ participant announces, which is what the allowlist was for. -->
126
+
127
+ <!-- What the removed descriptor's sendBufferSize/receiveBufferSize were.
128
+ These are participant-level and asymmetric on purpose: the receive
129
+ side absorbs a burst that arrives after a stall, the send side
130
+ should not hold one. -->
131
+ <sendSocketBufferSize>1048576</sendSocketBufferSize>
132
+ <listenSocketBufferSize>4194304</listenSocketBufferSize>
135
133
  </rtps>
136
134
  </participant>
137
135
 
@@ -0,0 +1,93 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <dds xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
3
+ <profiles>
4
+ <participant profile_name="rosotacom_fastdds_unicast" is_default_profile="true">
5
+ <rtps>
6
+ <defaultUnicastLocatorList>
7
+ <locator>
8
+ <udpv4>
9
+ <address>#host_ip</address>
10
+ <port>0</port>
11
+ </udpv4>
12
+ </locator>
13
+ </defaultUnicastLocatorList>
14
+
15
+ <builtin>
16
+ <metatrafficUnicastLocatorList>
17
+ <locator>
18
+ <udpv4>
19
+ <address>#host_ip</address>
20
+ <port>0</port>
21
+ </udpv4>
22
+ </locator>
23
+ </metatrafficUnicastLocatorList>
24
+
25
+ <initialPeersList>
26
+ <locator>
27
+ <udpv4>
28
+ <address>#host_ip</address>
29
+ <port>0</port>
30
+ </udpv4>
31
+ </locator>
32
+ <!--peer-block--><locator>
33
+ <udpv4>
34
+ <address>#peer</address>
35
+ <port>0</port>
36
+ </udpv4>
37
+ </locator><!--/peer-block-->
38
+ </initialPeersList>
39
+ </builtin>
40
+
41
+ <!-- No transport element here, deliberately, and this is load-bearing.
42
+
43
+ Measured on a two-container bench (2026-08-19, ROS 2 Kilted, Fast
44
+ DDS 3.2.4, rmw_fastrtps_cpp 9.3.4): a participant profile that
45
+ carries *any* transport element transmits every sample twice.
46
+
47
+ <userTransports> + <useBuiltinTransports>false 1741 kbit/s
48
+ <builtinTransports non_blocking="true">UDPv4 1741 kbit/s
49
+ <builtinTransports>UDPv4 1741 kbit/s
50
+ <builtinTransports>DEFAULT 1741 kbit/s
51
+ no transport element at all 1007 kbit/s
52
+
53
+ against a 960 kbit/s payload. The DEFAULT row is what settles it:
54
+ that selection is a no-op — it asks for exactly the built-in set —
55
+ and it still doubles. Counted per datagram, 366 to the reader's data
56
+ locator for 180 published samples against 183 without the element,
57
+ each carrying a full 11 951 B of UDP payload to the same address and
58
+ port. The reader discards the second copy by sequence number, so it
59
+ buys nothing and costs half the link.
60
+
61
+ What removing it gives up, named rather than hidden:
62
+ * shared memory is enabled again. It is unused between machines,
63
+ and between processes on one host it is faster than the loopback
64
+ path this profile also names. fastdds_local.xml records that SHM
65
+ was measured and is not the cause of the local-domain failure.
66
+ * <non_blocking_send>true</non_blocking_send> is gone with the
67
+ descriptor, so a full send buffer blocks instead of dropping.
68
+ That is the smaller cost: a blocking send stalls the writer for
69
+ as long as the link is behind, where the duplicate costs half the
70
+ link all the time.
71
+ * maxInitialPeersRange falls back to 4 from 10, and the interface
72
+ allowlist is gone. The locator lists above already pin what this
73
+ participant announces, which is what the allowlist was for. -->
74
+ </rtps>
75
+ </participant>
76
+
77
+ <data_writer profile_name="rosotacom_fastdds_writer" is_default_profile="true">
78
+ <qos>
79
+ <data_sharing>
80
+ <kind>OFF</kind>
81
+ </data_sharing>
82
+ </qos>
83
+ </data_writer>
84
+
85
+ <data_reader profile_name="rosotacom_fastdds_reader" is_default_profile="true">
86
+ <qos>
87
+ <data_sharing>
88
+ <kind>OFF</kind>
89
+ </data_sharing>
90
+ </qos>
91
+ </data_reader>
92
+ </profiles>
93
+ </dds>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev58
3
+ Version: 2.5.dev60
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -61,6 +61,7 @@ docs/release.md
61
61
  docs/testing.md
62
62
  docs/work-items.md
63
63
  docs/findings/README.md
64
+ docs/findings/a-transport-element-doubles-every-fastdds-sample.md
64
65
  docs/findings/cyclone-spdp-discovery-bursts.md
65
66
  docs/findings/delay-alone-no-loss.md
66
67
  docs/findings/fastdds-datagram-cap-drops-large-samples.md
@@ -68,6 +69,7 @@ docs/findings/graph-derived-types-block-a-non-graph-transport.md
68
69
  docs/findings/head-of-line-irregularity.md
69
70
  docs/findings/jitter-loss-bandwidth-latency.md
70
71
  docs/findings/keyframe-loss-kills-the-next-deltas.md
72
+ docs/findings/lifespan-is-enforced-by-one-reader-and-not-the-other.md
71
73
  docs/findings/lighter-message-loss.md
72
74
  docs/findings/oversubscription-queues-not-losses.md
73
75
  docs/findings/reorder-becomes-reader-loss.md
@@ -46,6 +46,7 @@
46
46
  "docs/ci.md",
47
47
  "docs/ffmpeg-keyframes.md",
48
48
  "docs/findings/README.md",
49
+ "docs/findings/a-transport-element-doubles-every-fastdds-sample.md",
49
50
  "docs/findings/cyclone-spdp-discovery-bursts.md",
50
51
  "docs/findings/delay-alone-no-loss.md",
51
52
  "docs/findings/fastdds-datagram-cap-drops-large-samples.md",
@@ -53,6 +54,7 @@
53
54
  "docs/findings/head-of-line-irregularity.md",
54
55
  "docs/findings/jitter-loss-bandwidth-latency.md",
55
56
  "docs/findings/keyframe-loss-kills-the-next-deltas.md",
57
+ "docs/findings/lifespan-is-enforced-by-one-reader-and-not-the-other.md",
56
58
  "docs/findings/lighter-message-loss.md",
57
59
  "docs/findings/oversubscription-queues-not-losses.md",
58
60
  "docs/findings/reorder-becomes-reader-loss.md",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "2.4",
3
- "distance": 58,
4
- "node": "ge746c2a24a5de807925ed1cc3b61921a542c2ac2",
3
+ "distance": 60,
4
+ "node": "g3cd0295dbb4fd3e1533c5da3267cfa2ba8b2b58c",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-08-19"
@@ -702,3 +702,97 @@ def test_sigterm_disposition_is_restored() -> None:
702
702
  with rosotacom._terminate_via_exception():
703
703
  assert signal.getsignal(signal.SIGTERM) is not before
704
704
  assert signal.getsignal(signal.SIGTERM) is before
705
+
706
+
707
+ def _reach_peer() -> rosotacom.OtaSmokePeer:
708
+ return rosotacom.OtaSmokePeer("a", "robot-a", "10.0.0.10")
709
+
710
+
711
+ def test_reach_check_retries_a_transport_that_flaked(
712
+ monkeypatch: pytest.MonkeyPatch,
713
+ capsys: pytest.CaptureFixture[str],
714
+ ) -> None:
715
+ """An ssh handshake that times out under load is not a verdict about the peer.
716
+
717
+ The probe is `true`, so a non-zero exit can only be the transport. That is
718
+ what makes retrying safe here and nowhere else in the preflight.
719
+ """
720
+ attempts: list[str] = []
721
+
722
+ def fake_ota_run(peer: object, script: str, **kwargs: object) -> subprocess.CompletedProcess[str]:
723
+ attempts.append(script)
724
+ if len(attempts) < 3:
725
+ return subprocess.CompletedProcess([script], 255, "", "ssh: connect to host port 22: Connection timed out")
726
+ return subprocess.CompletedProcess([script], 0, "", "")
727
+
728
+ monkeypatch.setattr(rosotacom, "_ota_run", fake_ota_run)
729
+ monkeypatch.setattr(rosotacom.time, "sleep", lambda _seconds: None)
730
+
731
+ rosotacom._ota_reach_check(_reach_peer(), dry_run=False)
732
+
733
+ assert len(attempts) == 3
734
+ out = capsys.readouterr().out
735
+ # The run must not read as a clean first-try connection.
736
+ assert "attempt 3 of 3" in out
737
+ assert "flaky" in out
738
+
739
+
740
+ def test_reach_check_retries_a_timeout_too(monkeypatch: pytest.MonkeyPatch) -> None:
741
+ calls: list[int] = []
742
+
743
+ def fake_ota_run(peer: object, script: str, **kwargs: object) -> subprocess.CompletedProcess[str]:
744
+ calls.append(1)
745
+ if len(calls) == 1:
746
+ raise subprocess.TimeoutExpired(cmd=["ssh"], timeout=20.0)
747
+ return subprocess.CompletedProcess([script], 0, "", "")
748
+
749
+ monkeypatch.setattr(rosotacom, "_ota_run", fake_ota_run)
750
+ monkeypatch.setattr(rosotacom.time, "sleep", lambda _seconds: None)
751
+
752
+ rosotacom._ota_reach_check(_reach_peer(), dry_run=False)
753
+ assert len(calls) == 2
754
+
755
+
756
+ def test_reach_check_gives_up_and_says_how_often_it_tried(monkeypatch: pytest.MonkeyPatch) -> None:
757
+ calls: list[int] = []
758
+
759
+ def fake_ota_run(peer: object, script: str, **kwargs: object) -> subprocess.CompletedProcess[str]:
760
+ calls.append(1)
761
+ return subprocess.CompletedProcess([script], 255, "", "ssh: Could not resolve hostname robot-a")
762
+
763
+ monkeypatch.setattr(rosotacom, "_ota_run", fake_ota_run)
764
+ monkeypatch.setattr(rosotacom.time, "sleep", lambda _seconds: None)
765
+
766
+ with pytest.raises(RuntimeError) as excinfo:
767
+ rosotacom._ota_reach_check(_reach_peer(), dry_run=False)
768
+
769
+ assert len(calls) == rosotacom._REACH_ATTEMPTS
770
+ message = str(excinfo.value)
771
+ assert "after 3 attempts" in message
772
+ assert "Could not resolve hostname" in message
773
+
774
+
775
+ def test_the_load_check_does_not_retry(
776
+ tmp_path: Path,
777
+ monkeypatch: pytest.MonkeyPatch,
778
+ ) -> None:
779
+ """Only the reachability probe retries.
780
+
781
+ A peer somebody else is saturating is still saturated a moment later, and a
782
+ container that is already running is still running. Retrying those would
783
+ turn a real refusal into a delayed one, which is the failure mode the load
784
+ check exists to prevent.
785
+ """
786
+ calls: list[str] = []
787
+
788
+ def fake_ota_run(peer: object, script: str, **kwargs: object) -> subprocess.CompletedProcess[str]:
789
+ calls.append(script)
790
+ return subprocess.CompletedProcess([script], 0, "78.24 81.86 81.28\n32\n", "")
791
+
792
+ monkeypatch.setattr(rosotacom, "_ota_run", fake_ota_run)
793
+ monkeypatch.setattr(rosotacom.time, "sleep", lambda _seconds: pytest.fail("the load check must not sleep"))
794
+
795
+ with pytest.raises(RuntimeError):
796
+ rosotacom._ota_load_check(_load_plan(tmp_path), dry_run=False)
797
+
798
+ assert len(calls) == 1