rosotacom-dev 2.5.dev54__tar.gz → 2.5.dev55__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 (378) hide show
  1. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/testing.md +35 -0
  3. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  4. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/SOURCES.txt +1 -0
  5. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/scm_file_list.json +1 -0
  6. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  7. rosotacom_dev-2.5.dev55/tests/contract/test_generated_topics_have_a_producer.py +467 -0
  8. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.devcontainer/devcontainer.json.template +0 -0
  9. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  10. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/CODEOWNERS +0 -0
  11. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  12. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  13. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  14. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  15. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  16. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  17. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  18. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  19. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/actions/setup-just/action.yml +0 -0
  21. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/dependabot.yml +0 -0
  22. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/benchmark-calibrate.yml +0 -0
  23. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/benchmark-gate.yml +0 -0
  24. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/dev-release.yml +0 -0
  25. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/image-scan.yml +0 -0
  26. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/nightly-e2e.yml +0 -0
  27. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/pr-lightweight.yml +0 -0
  28. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/pr-merge-gate.yml +0 -0
  29. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.github/workflows/release.yml +0 -0
  30. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/.pre-commit-config.yaml +0 -0
  31. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/AGENTS.md +0 -0
  32. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/CONCURRENCY.md +0 -0
  33. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/CONTRIBUTING.md +0 -0
  34. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/DEVELOPMENT_PRINCIPLES.md +0 -0
  35. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/LICENSE.txt +0 -0
  36. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/MANIFEST.in +0 -0
  37. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/README.md +0 -0
  38. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/budgets.jsonl +0 -0
  39. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/calibration/README.md +0 -0
  40. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/calibration/capacity-size-tight-cyclone.json +0 -0
  41. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  42. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/calibration/probe-loss-nominal-cyclone.json +0 -0
  43. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/calibration/probe-loss-tight-cyclone.json +0 -0
  44. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/calibration/probe-loss-tight-fastdds.json +0 -0
  45. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/calibration/probe-loss-tight-zenoh.json +0 -0
  46. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/codecov.yml +0 -0
  47. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/deployment-configuration.md +0 -0
  48. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/benchmark-ab.md +0 -0
  49. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/benchmark-bag-as-load.md +0 -0
  50. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/ci.md +0 -0
  51. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/ffmpeg-keyframes.md +0 -0
  52. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/README.md +0 -0
  53. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  54. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/delay-alone-no-loss.md +0 -0
  55. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
  56. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
  57. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/head-of-line-irregularity.md +0 -0
  58. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  59. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
  60. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/lighter-message-loss.md +0 -0
  61. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/oversubscription-queues-not-losses.md +0 -0
  62. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/reorder-becomes-reader-loss.md +0 -0
  63. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/repro/decode_chain_repro.py +0 -0
  64. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/repro/reorder_drop_repro.py +0 -0
  65. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
  66. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  67. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/forensics-report.md +0 -0
  68. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/geo-link-map.md +0 -0
  69. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/github-repository-settings.md +0 -0
  70. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/link-bandwidth.md +0 -0
  71. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/link-trace.md +0 -0
  72. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/owner-runbook.md +0 -0
  73. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/performance-bands.md +0 -0
  74. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/playbooks/README.md +0 -0
  75. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/quality-model.md +0 -0
  76. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/release-notes/TEMPLATE.md +0 -0
  77. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/release-notes/v2.1.md +0 -0
  78. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/release-notes/v2.2.md +0 -0
  79. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/release-notes/v2.3.md +0 -0
  80. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/release-notes/v2.4.md +0 -0
  81. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/release.md +0 -0
  82. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  83. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/0002-expectation-concepts.md +0 -0
  84. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/0003-metric-backbone.md +0 -0
  85. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/0004-network-profiles.md +0 -0
  86. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  87. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/0006-dynamic-qos.md +0 -0
  88. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/0007-regression-gate.md +0 -0
  89. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/rfcs/README.md +0 -0
  90. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/docs/work-items.md +0 -0
  91. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/install.sh +0 -0
  92. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/justfile +0 -0
  93. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/media/control_center.png +0 -0
  94. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/media/echo_test.png +0 -0
  95. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/media/forensics-report-example.png +0 -0
  96. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/media/host.png +0 -0
  97. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/media/vehicle.png +0 -0
  98. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/pyproject.toml +0 -0
  99. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/requirements.txt +0 -0
  100. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/rosotacom.yaml +0 -0
  101. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/session-configuration.md +0 -0
  102. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/setup.cfg +0 -0
  103. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/__init__.py +0 -0
  104. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/__main__.py +0 -0
  105. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/anonymize.py +0 -0
  106. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/bag_ground_truth.py +0 -0
  107. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/benched_set.py +0 -0
  108. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/benchmark.py +0 -0
  109. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/benchmark_delta.py +0 -0
  110. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/bundle_check.py +0 -0
  111. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/cli.py +0 -0
  112. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/cli_benchmark.py +0 -0
  113. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/deployment.py +0 -0
  114. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/ffmpeg_packet.py +0 -0
  115. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/forensics.py +0 -0
  116. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/geomap.py +0 -0
  117. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/image_cache.py +0 -0
  118. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/netem_dist.py +0 -0
  119. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/network_preflight.py +0 -0
  120. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/network_profiles.py +0 -0
  121. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/network_shaper.py +0 -0
  122. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/plots.py +0 -0
  123. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/py.typed +0 -0
  124. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/benched-set.yaml +0 -0
  125. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/.gitignore +0 -0
  126. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/README.md +0 -0
  127. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/__init__.py +0 -0
  128. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  129. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  130. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
  131. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/profiles.yaml +0 -0
  132. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  133. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  134. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  135. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  136. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  137. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  138. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  139. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  140. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  141. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  142. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  143. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  144. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  145. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  146. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  147. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  148. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  149. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  150. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  151. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  152. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  153. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  154. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  155. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  156. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  157. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  158. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  159. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  160. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  161. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  162. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  163. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  164. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  165. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  166. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  167. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  168. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  169. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  170. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  171. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  172. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  173. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  174. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  175. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  176. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  177. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  178. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  179. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  180. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  181. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  182. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  183. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  184. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  185. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  186. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  187. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  188. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  189. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  190. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  191. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  192. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  193. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  195. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  196. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  198. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  199. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  200. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  201. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  202. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  203. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ros2docker.json.example +0 -0
  204. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  205. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  206. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  207. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  208. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  209. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  210. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  211. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  212. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  213. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  214. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  215. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  216. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
  217. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +0 -0
  218. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  219. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
  220. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  221. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  222. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  223. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  224. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  225. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  226. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  227. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  228. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  229. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  230. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  231. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  232. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  233. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  234. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  235. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  236. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  237. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  238. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  239. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  240. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  241. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  242. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
  243. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  244. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  245. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  246. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  247. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  248. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  249. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  250. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  251. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  252. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  253. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  254. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  255. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  256. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  257. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  258. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  259. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  260. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  261. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
  262. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
  263. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  264. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  265. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  266. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  267. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  268. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  269. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  270. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  271. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  272. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  273. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  274. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  275. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  276. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  277. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  278. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  279. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  280. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  281. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  282. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  283. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  284. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  285. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  286. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  287. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  288. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
  289. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  290. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  291. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  292. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  293. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  294. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
  295. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  296. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  297. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  298. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/status_eval.py +0 -0
  299. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/stream_stats.py +0 -0
  300. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/trace_profiles.py +0 -0
  301. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/transit.py +0 -0
  302. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom/video_quality.py +0 -0
  303. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  304. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  305. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  306. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  307. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/terminology.md +0 -0
  308. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_benched_set_registry.py +0 -0
  309. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_findings.py +0 -0
  310. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_markdown_links.py +0 -0
  311. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_ota_source_bundle.py +0 -0
  312. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_package_metadata.py +0 -0
  313. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_packaged_resources.py +0 -0
  314. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  315. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_pytest_policy.py +0 -0
  316. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_readme_examples.py +0 -0
  317. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_security_maintenance_config.py +0 -0
  318. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_session_configuration_doc.py +0 -0
  319. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_session_template_substitution.py +0 -0
  320. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/contract/test_workflow_contracts.py +0 -0
  321. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/conftest.py +0 -0
  322. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_anonymize_e2e.py +0 -0
  323. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_benchmark_ab.py +0 -0
  324. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_benchmark_capacity.py +0 -0
  325. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_benchmark_replay.py +0 -0
  326. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_parallel_smoke.py +0 -0
  327. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_smoke.py +0 -0
  328. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_timeline_stepping.py +0 -0
  329. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/e2e/test_video_quality_e2e.py +0 -0
  330. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/sessions/generate_rmw_matrix.py +0 -0
  331. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  332. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  333. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  334. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  335. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
  336. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  337. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_anonymize.py +0 -0
  338. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_bag_ground_truth.py +0 -0
  339. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_benched_set.py +0 -0
  340. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_benchmark.py +0 -0
  341. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_benchmark_delta.py +0 -0
  342. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_bundle_check.py +0 -0
  343. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_catmux_pane_logging.py +0 -0
  344. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_cli.py +0 -0
  345. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_cli_benchmark.py +0 -0
  346. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_concurrency.py +0 -0
  347. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_container_shaping.py +0 -0
  348. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_deployment.py +0 -0
  349. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_ffmpeg_flags.py +0 -0
  350. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_ffmpeg_packet.py +0 -0
  351. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_field_profiles.py +0 -0
  352. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_forensics.py +0 -0
  353. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_geomap.py +0 -0
  354. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_image_cache.py +0 -0
  355. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_link_bytes.py +0 -0
  356. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_link_trace.py +0 -0
  357. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_netem_dist.py +0 -0
  358. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_network_preflight.py +0 -0
  359. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_network_profiles.py +0 -0
  360. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_network_shaper.py +0 -0
  361. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_ota_link_configs.py +0 -0
  362. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_ota_unwrapper.py +0 -0
  363. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_ota_wrapper.py +0 -0
  364. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_ota_xml.py +0 -0
  365. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_playout_pacer.py +0 -0
  366. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_plots.py +0 -0
  367. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_pub_sub_pair_types.py +0 -0
  368. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_qos_fallback_logging.py +0 -0
  369. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_relay_optional_parameters.py +0 -0
  370. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_session_pipeline.py +0 -0
  371. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_stage_latency.py +0 -0
  372. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_status_eval.py +0 -0
  373. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_status_overview.py +0 -0
  374. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_status_overview_observer.py +0 -0
  375. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_stream_stats.py +0 -0
  376. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_trace_profiles.py +0 -0
  377. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/tests/unit/test_transit.py +0 -0
  378. {rosotacom_dev-2.5.dev54 → rosotacom_dev-2.5.dev55}/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.dev54
3
+ Version: 2.5.dev55
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -25,6 +25,41 @@ slice. The full local suite also runs nightly, as does the blocking benchmark
25
25
  regression matrix ([performance-bands.md](performance-bands.md)). The external
26
26
  OTA gate remains operator-started before a promotion to `main`.
27
27
 
28
+ ## Every read has a producer
29
+
30
+ `tests/contract/test_generated_topics_have_a_producer.py` generates every
31
+ example session and asserts that each topic a node is *configured to read* is
32
+ either written by another node on the same peer or a stage that peer's own
33
+ `pipeline_spec.yaml` models. Writes are deliberately not required to be stages:
34
+ a node may publish for a human (`NOR` restores `/tf`, the pacer publishes
35
+ `budget_ms`) and none of that is something the link promises to deliver.
36
+
37
+ It exists because this project's characteristic defect — #12, #259, #276, #304 —
38
+ is always the same one: a ROS node advertises its output whether or not its
39
+ input exists, so a node pointed at a name nobody publishes starts, stays up,
40
+ reports healthy and delivers nothing. #304 was the first of the four caught by
41
+ any check at all, and that check was a six-minute Docker slice covering one
42
+ example. This one runs the same corpus in about a second.
43
+
44
+ Two guards keep it honest, and both fail loudly rather than degrading:
45
+
46
+ - every parameter `session_plugin_base.yaml` declares must be classified in the
47
+ check, so a new window cannot arrive unmodelled and be silently covered;
48
+ - the composed names the check derives (`<topic>/paced`, `<topic>/drop1of2`, …)
49
+ are asserted to be the forms the template itself composes.
50
+
51
+ It also pins **which window families the examples actually exercise**. Today
52
+ `fb`, `ipx`, `nor` and `pace` are exercised by no example, so the check models
53
+ them unverified — `pace` most notably, since the playout pacer is deployed on a
54
+ real link. Tracked in
55
+ [#307](https://github.com/develNor/ros_communication_devcontainer/issues/307);
56
+ adding an example that uses one of them is the way to close it.
57
+
58
+ The check covers reads by nodes *the session configures*. It cannot see an
59
+ application outside the session subscribing to a name the link no longer
60
+ delivers — that half of #12 and #276 needs the consumer to be declared
61
+ somewhere, and nothing declares it today.
62
+
28
63
  ## Expectations
29
64
 
30
65
  Per-topic `expect` blocks are behavioral contracts in `session-definition.yaml`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev54
3
+ Version: 2.5.dev55
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -304,6 +304,7 @@ src/rosotacom_dev.egg-info/scm_version.json
304
304
  src/rosotacom_dev.egg-info/top_level.txt
305
305
  tests/contract/test_benched_set_registry.py
306
306
  tests/contract/test_findings.py
307
+ tests/contract/test_generated_topics_have_a_producer.py
307
308
  tests/contract/test_markdown_links.py
308
309
  tests/contract/test_ota_source_bundle.py
309
310
  tests/contract/test_package_metadata.py
@@ -299,6 +299,7 @@
299
299
  "terminology.md",
300
300
  "tests/contract/test_benched_set_registry.py",
301
301
  "tests/contract/test_findings.py",
302
+ "tests/contract/test_generated_topics_have_a_producer.py",
302
303
  "tests/contract/test_markdown_links.py",
303
304
  "tests/contract/test_ota_source_bundle.py",
304
305
  "tests/contract/test_package_metadata.py",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "2.4",
3
- "distance": 54,
4
- "node": "g28ff3da22234e297b05850ec0df7553546665959",
3
+ "distance": 55,
4
+ "node": "gd772be38947b2d3b6d4fc5d45d9fa57da1e1723a",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-08-19"
@@ -0,0 +1,467 @@
1
+ """A generated session may not tell a node to read a topic nothing publishes.
2
+
3
+ This repository's characteristic defect has now shipped four times, always in
4
+ the same shape and never with a symptom:
5
+
6
+ * #12 a wrapper renamed the delivered topic; the centre stayed subscribed to
7
+ the old name and every panel stayed green.
8
+ * #259 the wrapper ran before the encoder, so the envelope carried an image
9
+ while the receiver decoded a name that never appeared.
10
+ * #276 the reverse republish decoded to `raw` while the consumer subscribed to
11
+ `.../compressed`; every intervention snapshot was written without its
12
+ camera.
13
+ * #304 a decode was pointed at `<encoded>/paced` on a link that paces nothing.
14
+
15
+ The mechanism is the same every time and it is why none of them failed loudly:
16
+ a ROS node advertises its output whether or not its input exists. Point it at a
17
+ name nobody publishes and it starts, stays up, reports healthy, and delivers
18
+ nothing. Only #304 was caught by a check at all, and that check was a
19
+ six-minute Docker slice that exists for one example session.
20
+
21
+ So the invariant is checked here, in the fast lane, against every example:
22
+
23
+ every topic a node is configured to READ is either written by another node
24
+ the same peer configures, or a stage that peer's own pipeline spec knows
25
+
26
+ Writes are deliberately *not* required to be stages. A node may publish for a
27
+ human -- the NOR window restores `/tf` for a local TF tree, the pacer publishes
28
+ `budget_ms` -- and none of that is something the link promises to deliver. It
29
+ is the read side that can be wrong in silence.
30
+
31
+ `pipeline_spec.yaml` is the right authority for the other half because the
32
+ generator builds it on a different path from the plugin parameters: the spec is
33
+ the model, the parameters are what the nodes are actually told. #304 was
34
+ precisely a divergence between the two, with each half self-consistent.
35
+
36
+ Two guards keep this check honest as the template grows:
37
+
38
+ * every parameter the template declares must be classified here, so a new
39
+ window cannot arrive unmodelled and silently covered;
40
+ * every topic name this file composes (`<topic>/paced`, `<topic>/drop1of2`, ...)
41
+ is asserted to be the form the template itself composes, so a renamed suffix
42
+ fails here instead of drifting apart.
43
+
44
+ Offline: no Docker, no ROS, no running session.
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import importlib.util
50
+ import re
51
+ import sys
52
+ from collections.abc import Iterator
53
+ from dataclasses import dataclass, field
54
+ from pathlib import Path
55
+ from typing import Any
56
+
57
+ import pytest
58
+ import yaml
59
+
60
+ REPO_ROOT = Path(__file__).resolve().parents[2]
61
+ WS = REPO_ROOT / "src" / "rosotacom" / "resources" / "ws"
62
+ GENERATOR_PY = WS / "session" / "creation" / "generate_session_files.py"
63
+ PLUGIN_BASE = WS / "session" / "content" / "base" / "session_plugin_base.yaml"
64
+ PACER_PY = WS / "ros2src" / "com_py" / "com_py" / "playout_pacer.py"
65
+ EXAMPLES = REPO_ROOT / "src" / "rosotacom" / "resources" / "examples" / "sessions"
66
+
67
+
68
+ def _load_generator() -> Any:
69
+ spec = importlib.util.spec_from_file_location("rosotacom_generate_session_files_topics", GENERATOR_PY)
70
+ assert spec and spec.loader
71
+ module = importlib.util.module_from_spec(spec)
72
+ sys.modules[spec.name] = module
73
+ spec.loader.exec_module(module)
74
+ return module
75
+
76
+
77
+ generator = _load_generator()
78
+
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # reading the generated files
82
+ # ---------------------------------------------------------------------------
83
+
84
+ #: catmux renders an unset parameter as the literal string "None".
85
+ UNSET = ("", "None", None)
86
+
87
+
88
+ def _one(params: dict, key: str) -> str | None:
89
+ value = params.get(key)
90
+ if isinstance(value, bool) or value in UNSET:
91
+ return None
92
+ return str(value)
93
+
94
+
95
+ def _many(params: dict, key: str) -> list[str]:
96
+ """A comma-separated parameter, as the template hands it to a node."""
97
+ value = _one(params, key)
98
+ return [item for item in (value or "").split(",") if item]
99
+
100
+
101
+ def _set(params: dict, key: str) -> bool:
102
+ """Is a flag-shaped parameter present at all (the template tests != None)."""
103
+ return _one(params, key) is not None or params.get(key) is True
104
+
105
+
106
+ #: `^...$` around a literal name, which is how the compressor/wrapper config
107
+ #: files list their topics. Anything with real regex syntax is a pattern rather
108
+ #: than a name and cannot be resolved statically.
109
+ _ANCHORED_LITERAL = re.compile(r"^\^(?P<topic>/[A-Za-z0-9_/]*)\$$")
110
+
111
+
112
+ def _config_file_topics(peer_dir: Path, params: dict, key: str) -> list[str]:
113
+ """The topics a `*_config_file` node subscribes to."""
114
+ path = _one(params, key)
115
+ if path is None:
116
+ return []
117
+ resolved = peer_dir / Path(path).name
118
+ if not resolved.is_file():
119
+ return []
120
+ document = yaml.safe_load(resolved.read_text(encoding="utf-8")) or {}
121
+ topics: list[str] = []
122
+ for entries in document.values():
123
+ for entry in entries or []:
124
+ match = _ANCHORED_LITERAL.match(str((entry or {}).get("topic_regex", "")))
125
+ if match:
126
+ topics.append(match.group("topic"))
127
+ return topics
128
+
129
+
130
+ # ---------------------------------------------------------------------------
131
+ # what each window touches
132
+ # ---------------------------------------------------------------------------
133
+
134
+
135
+ @dataclass
136
+ class Touches:
137
+ reads: set[str] = field(default_factory=set)
138
+ writes: set[str] = field(default_factory=set)
139
+
140
+ def through(self, source: str, target: str) -> None:
141
+ self.reads.add(source)
142
+ self.writes.add(target)
143
+
144
+
145
+ def _slots(params: dict, template: str, limit: int = 8) -> Iterator[int]:
146
+ """Indices whose slot is filled, for the indexed window families."""
147
+ for index in range(1, limit + 1):
148
+ if _one(params, template.format(index)) is not None:
149
+ yield index
150
+
151
+
152
+ def touches(params: dict, peer_dir: Path) -> Touches:
153
+ """Every topic this peer's parameters put on a node's input or output.
154
+
155
+ One function rather than a rule table, because most windows are a single
156
+ `source -> target` pair and reading them next to each other is how a wrong
157
+ one is spotted. The composed suffixes are asserted against the template in
158
+ `test_the_composed_names_are_the_ones_the_template_composes`.
159
+ """
160
+ result = Touches()
161
+
162
+ # BEAT: this peer answers the remote heartbeat with its own.
163
+ if remote := _one(params, "heartbeat_remote_topic"):
164
+ result.reads.add(remote)
165
+ if local := _one(params, "heartbeat_local_topic"):
166
+ result.writes.add(local)
167
+
168
+ # RS / LAT / TRICKLE: same shape, each appends its own suffix.
169
+ for topics_key, suffix_key in (
170
+ ("rs_restamp_topics", "rs_topic_suffix"),
171
+ ("lat_topics", "lat_topic_suffix"),
172
+ ("trickle_topics", "trickle_topic_suffix"),
173
+ ):
174
+ suffix = _one(params, suffix_key) or ""
175
+ for topic in _many(params, topics_key):
176
+ result.through(topic, topic + suffix)
177
+
178
+ # FB: one direction adds the global suffix, the other takes it off again.
179
+ global_suffix = _one(params, "fb_global_topic_suffix") or ""
180
+ for topic in _many(params, "fb_local_to_global_topics"):
181
+ result.through(topic, topic + global_suffix)
182
+ for topic in _many(params, "fb_global_to_local_topics"):
183
+ result.through(topic + global_suffix, topic)
184
+
185
+ # DRP / THR: topic_tools, output named after the parameters themselves.
186
+ for index in _slots(params, "drp_topic_{}"):
187
+ topic = _one(params, f"drp_topic_{index}")
188
+ count = _one(params, f"drp_drop_count_{index}")
189
+ window = _one(params, f"drp_window_size_{index}")
190
+ result.through(str(topic), f"{topic}/drop{count}of{window}")
191
+ for index in _slots(params, "thr_topic_{}", limit=4):
192
+ topic = _one(params, f"thr_topic_{index}")
193
+ rate = _one(params, f"thr_rate_{index}")
194
+ result.through(str(topic), f"{topic}/max{rate}hz")
195
+
196
+ # IPX: the preset names the output.
197
+ for index in _slots(params, "ipx_{}_topics", limit=4):
198
+ preset = _one(params, f"ipx_{index}_preset")
199
+ for topic in _many(params, f"ipx_{index}_topics"):
200
+ result.through(topic, f"{topic}/{preset}")
201
+
202
+ # IT: the sender's encoder.
203
+ for index in _slots(params, "it_{}_topic", limit=4):
204
+ topic = _one(params, f"it_{index}_topic")
205
+ outgoing = _one(params, f"it_{index}_outgoing_suffix") or _one(params, f"it_{index}_transport")
206
+ result.through(str(topic), f"{topic}/{outgoing}")
207
+
208
+ # IRT: the reverse republish. Its input moves to the paced copy when the
209
+ # link paces, and its output name is overridable so a second slot can decode
210
+ # the same stream for comparison.
211
+ for index in _slots(params, "irt_{}_topic", limit=4):
212
+ topic = _one(params, f"irt_{index}_topic")
213
+ source = f"{topic}/paced" if _set(params, f"irt_{index}_paced") else str(topic)
214
+ target = _one(params, f"irt_{index}_out_topic") or f"{topic}/{_one(params, f'irt_{index}_out_transport')}"
215
+ result.through(source, target)
216
+
217
+ # PACE: the playout pacer, plus the two debug topics it publishes.
218
+ for index in _slots(params, "pace_{}_topic", limit=4):
219
+ topic = _one(params, f"pace_{index}_topic")
220
+ result.through(str(topic), f"{topic}/paced")
221
+ result.writes.update({f"{topic}/paced/budget_ms", f"{topic}/paced/queue_depth"})
222
+
223
+ # NOR: relays a prefixed, suffixed name back onto the bare base, so a local
224
+ # consumer (a TF tree) finds the name it expects.
225
+ for index in _slots(params, "nor_topic_{}_base", limit=4):
226
+ prefix = _one(params, f"nor_topic_{index}_prefix") or ""
227
+ base = _one(params, f"nor_topic_{index}_base")
228
+ suffix = _one(params, f"nor_topic_{index}_suffix") or ""
229
+ result.through(f"{prefix}/{base}{suffix}", f"/{base}")
230
+
231
+ # COMP / DECO / OTAW / OTAU: the topic list lives in a config file, and each
232
+ # node adds or removes its own suffix.
233
+ for config_key, suffix_key, adds in (
234
+ ("comp_config_file", "comp_algorithm", True),
235
+ ("deco_config_file", "deco_algorithm", False),
236
+ ("otaw_config_file", "otaw_suffix", True),
237
+ ("otau_config_file", "otau_suffix", False),
238
+ ):
239
+ raw = _one(params, suffix_key) or ""
240
+ suffix = raw if raw.startswith("/") else f"/{raw}"
241
+ for topic in _config_file_topics(peer_dir, params, config_key):
242
+ # The listed topic is the node's *input* either way; `adds` says
243
+ # whether the output grows the suffix or is the input without it.
244
+ result.through(topic, topic + suffix if adds else topic[: -len(suffix)])
245
+
246
+ # Observability: reads only, never republishes.
247
+ result.reads.update(_many(params, "metric_stage_topics"))
248
+
249
+ return result
250
+
251
+
252
+ def spec_topics(peer_dir: Path) -> set[str]:
253
+ """Every topic this peer's pipeline spec models, at any stage."""
254
+ spec = yaml.safe_load((peer_dir / "pipeline_spec.yaml").read_text(encoding="utf-8"))
255
+ return {stage["topic"] for topic in spec.get("topics", []) for stage in topic.get("stages", [])}
256
+
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # the parameter classification, so nothing arrives unmodelled
260
+ # ---------------------------------------------------------------------------
261
+
262
+ #: Parameters whose value is a topic (or composes one) and which `touches`
263
+ #: therefore has to account for.
264
+ CARRY_TOPICS = (
265
+ r"heartbeat_(local|remote)_topic",
266
+ r"(rs_restamp|lat|trickle)_topics",
267
+ r"(rs|lat|trickle)_topic_suffix",
268
+ r"fb_(local_to_global|global_to_local)_topics",
269
+ r"fb_global_topic_suffix",
270
+ r"drp_topic_\d+",
271
+ r"drp_(drop_count|window_size)_\d+",
272
+ r"thr_topic_\d+",
273
+ r"thr_rate_\d+",
274
+ r"ipx_\d+_(topics|preset)",
275
+ r"it_\d+_(topic|transport|outgoing_suffix)",
276
+ r"irt_\d+_(topic|out_topic|out_transport|paced)",
277
+ r"pace_\d+_topic",
278
+ r"nor_topic_\d+_(prefix|base|suffix)",
279
+ r"metric_stage_topics",
280
+ r"(comp|deco|otaw|otau)_(config_file|algorithm|suffix)",
281
+ )
282
+
283
+ #: Parameters that name topics whose producers the *pipeline spec* owns: the
284
+ #: bridge and relay stages are computed from the topic list files by the same
285
+ #: naming rules the spec is built from, so re-deriving them here would compare
286
+ #: the generator with itself.
287
+ SPEC_OWNS = (
288
+ r"topic_list_paths_(inbound|outbound)",
289
+ r"(local_explicitly_targeted_inbound|app_keep_source_name_inbound)",
290
+ r"(app_has_source_name_outbound|remote_explicitly_targeted_name_outbound)",
291
+ )
292
+
293
+ #: Everything else: addresses, files, rates, flags, window switches, encoder
294
+ #: options. Listed by pattern so an unclassified newcomer fails the guard.
295
+ NO_TOPIC = (
296
+ r"dir_path|before_command|peer_dir|session_dir",
297
+ r"(rmw|ota|local)_(local|ota|config_template|config_file|easy_mode_ip|spdp_interval|domain_id|name)",
298
+ r"domain_bridge_config_file|use_domain_bridge",
299
+ r"ip_(local|remote)|remote_name|qos_config_file|topic_types_file",
300
+ r"in|out|use_zenoh_(rmw|ros2dds)",
301
+ r"zen_.*",
302
+ r"topic_monitor|tm_.*",
303
+ r"status_.*|link_trace.*|metric_stage_bag|network_monitor",
304
+ r"heartbeat|heartbeat_(out_hz|delay_bad_ms|loss3_bad_pct)",
305
+ r"rs|lat|trickle|fb|comp|deco|otaw|otau|nor|drp|drp2|thr|ipx|it|irt|pace",
306
+ r"lat_keepalive_hz|trickle_rate_hz",
307
+ r"fb_(global_frame_prefix|prefix_exclude_frames|tf_filter_frames|tf_throttle_links)",
308
+ r"it_\d+_(compressed_jpeg_quality|ffmpeg_\w+|foxglove_\w+)",
309
+ r"irt_\d+_transport",
310
+ r"pace_\d+_(msg_type|adaptive|target_ms|min_ms|max_ms)",
311
+ )
312
+
313
+
314
+ def _matches(patterns: tuple[str, ...], name: str) -> int:
315
+ return sum(1 for pattern in patterns if re.fullmatch(pattern, name))
316
+
317
+
318
+ def declared_parameters() -> list[str]:
319
+ return list(yaml.safe_load(PLUGIN_BASE.read_text(encoding="utf-8"))["parameters"])
320
+
321
+
322
+ def test_every_template_parameter_is_classified() -> None:
323
+ """A new window may not arrive unmodelled and be silently covered."""
324
+ unclassified = [name for name in declared_parameters() if _matches(CARRY_TOPICS + SPEC_OWNS + NO_TOPIC, name) == 0]
325
+
326
+ assert not unclassified, (
327
+ "these template parameters are in neither CARRY_TOPICS, SPEC_OWNS nor "
328
+ "NO_TOPIC, so this check does not know whether they name a topic:\n " + "\n ".join(unclassified)
329
+ )
330
+
331
+
332
+ def test_no_parameter_is_classified_twice() -> None:
333
+ """Two answers for one parameter means one of them is not being read."""
334
+ ambiguous = [name for name in declared_parameters() if _matches(CARRY_TOPICS + SPEC_OWNS + NO_TOPIC, name) > 1]
335
+
336
+ assert not ambiguous, "classified by more than one pattern:\n " + "\n ".join(ambiguous)
337
+
338
+
339
+ def test_the_composed_names_are_the_ones_the_template_composes() -> None:
340
+ """`touches` composes names; the template composes the same ones, or this drifts."""
341
+ template = PLUGIN_BASE.read_text(encoding="utf-8")
342
+ for composed in (
343
+ "${irt_1_topic}/paced",
344
+ "${irt_1_topic}/${irt_1_out_transport}",
345
+ "${drp_topic_1}/drop${drp_drop_count_1}of${drp_window_size_1}",
346
+ "${thr_topic_1}/max${thr_rate_1}hz",
347
+ "${nor_topic_1_prefix}/${nor_topic_1_base}${nor_topic_1_suffix}",
348
+ ):
349
+ assert composed in template, f"the template no longer composes {composed}"
350
+
351
+ # The pacer names its own output and its debug topics. The IRT window above
352
+ # reads that name, and `touches` claims those two writes, so all three have
353
+ # to agree -- they are three files apart and nothing else compares them.
354
+ pacer = PACER_PY.read_text(encoding="utf-8")
355
+ for composed in ('declare_parameter("topic_suffix", "/paced")', '+ "/budget_ms"', '+ "/queue_depth"'):
356
+ assert composed in pacer, f"the pacer no longer composes {composed}"
357
+
358
+
359
+ # ---------------------------------------------------------------------------
360
+ # the check itself
361
+ # ---------------------------------------------------------------------------
362
+
363
+
364
+ def example_sessions() -> list[Path]:
365
+ return sorted(path for path in EXAMPLES.iterdir() if (path / "session-definition.yaml").is_file())
366
+
367
+
368
+ def _generate(definition: Path, output_dir: Path) -> list[str]:
369
+ """Generate both peers of a session, and say which peers they are."""
370
+ document = yaml.safe_load((definition / "session-definition.yaml").read_text(encoding="utf-8"))
371
+ peers = list(document.get("peers") or document.get("peer_settings") or {})
372
+ generator.func(
373
+ session_config_yaml=str(definition / "session-definition.yaml"),
374
+ output_dir=str(output_dir),
375
+ force=True,
376
+ peer_addresses={peer: f"127.0.0.{index + 1}" for index, peer in enumerate(peers)},
377
+ )
378
+ return peers
379
+
380
+
381
+ @pytest.mark.parametrize("definition", example_sessions(), ids=lambda path: path.name)
382
+ def test_every_read_topic_has_something_that_publishes_it(definition: Path, tmp_path: Path) -> None:
383
+ peers = _generate(definition, tmp_path)
384
+ assert peers, f"{definition.name} declares no peers"
385
+
386
+ for peer in peers:
387
+ peer_dir = tmp_path / peer
388
+ params = yaml.safe_load((peer_dir / "plugin.yaml").read_text(encoding="utf-8"))["parameters"]
389
+ touched = touches(params, peer_dir)
390
+ produced = touched.writes | spec_topics(peer_dir)
391
+
392
+ orphans = sorted(topic for topic in touched.reads if topic not in produced)
393
+
394
+ assert not orphans, (
395
+ f"{definition.name}/{peer}: configured to read topics that neither "
396
+ f"another node here nor this peer's pipeline spec publishes. A node "
397
+ f"pointed at one of these advertises its output and delivers "
398
+ f"nothing:\n " + "\n ".join(orphans)
399
+ )
400
+
401
+
402
+ #: Which window families the example corpus actually puts a topic on. Pinned
403
+ #: rather than computed-and-ignored, because the value of this check is bounded
404
+ #: by it: a family no example exercises is a family this check never sees.
405
+ #:
406
+ #: Growing the set is good news -- update it. Shrinking it means an example
407
+ #: stopped exercising something and the check quietly got weaker, which is the
408
+ #: failure this pin exists to make loud.
409
+ FAMILIES_THE_EXAMPLES_EXERCISE = frozenset(
410
+ {"comp", "deco", "drp", "heartbeat", "irt", "it", "lat", "metric", "otau", "otaw", "rs", "thr", "trickle"}
411
+ )
412
+
413
+ #: The rest of what the template can emit. Each is configuration this check
414
+ #: models but no example ever produces, so the modelling is unverified:
415
+ #: `pace` most of all, since the pacer is deployed on a real link and #304 was
416
+ #: its wiring. Tracked in develNor/ros_communication_devcontainer#307.
417
+ FAMILIES_NO_EXAMPLE_EXERCISES = frozenset({"fb", "ipx", "nor", "pace"})
418
+
419
+
420
+ def _families_with_topics(params: dict) -> set[str]:
421
+ return {
422
+ key.split("_")[0]
423
+ for key, value in params.items()
424
+ if _one(params, key) is not None and _matches(CARRY_TOPICS, key)
425
+ }
426
+
427
+
428
+ def test_the_examples_exercise_the_windows_this_check_claims_to_cover(tmp_path: Path) -> None:
429
+ exercised: set[str] = set()
430
+ for index, definition in enumerate(example_sessions()):
431
+ output = tmp_path / str(index)
432
+ for peer in _generate(definition, output):
433
+ params = yaml.safe_load((output / peer / "plugin.yaml").read_text(encoding="utf-8"))["parameters"]
434
+ exercised |= _families_with_topics(params)
435
+
436
+ assert exercised == set(FAMILIES_THE_EXAMPLES_EXERCISE), (
437
+ "the example corpus no longer exercises what this pin records.\n"
438
+ f" gained: {sorted(exercised - FAMILIES_THE_EXAMPLES_EXERCISE)}\n"
439
+ f" lost: {sorted(FAMILIES_THE_EXAMPLES_EXERCISE - exercised)}\n"
440
+ "Gained is good news: add them here. Lost means this check got weaker."
441
+ )
442
+ assert not exercised & FAMILIES_NO_EXAMPLE_EXERCISES
443
+
444
+
445
+ def test_the_check_catches_the_defect_it_was_written_for(tmp_path: Path) -> None:
446
+ """#304 on a real session: a decode pointed at a paced copy, no pacer running.
447
+
448
+ Mutating the generated parameters rather than hand-writing a dict, so the
449
+ same path runs that the check runs -- `touches` against `spec_topics` of a
450
+ session that really exists. `17_synthetic_camera_quality` is the example the
451
+ defect actually broke.
452
+ """
453
+ definition = EXAMPLES / "17_synthetic_camera_quality"
454
+ peers = _generate(definition, tmp_path)
455
+ peer_dir = tmp_path / peers[0]
456
+ params = yaml.safe_load((peer_dir / "plugin.yaml").read_text(encoding="utf-8"))["parameters"]
457
+ assert params.get("irt_1_topic"), "this example is expected to decode on the receiver"
458
+ assert not _set(params, "irt_1_paced"), "this example declares no playout"
459
+
460
+ healthy = touches(params, peer_dir)
461
+ assert not healthy.reads - (healthy.writes | spec_topics(peer_dir))
462
+
463
+ params["irt_1_paced"] = "true" # what the generator wrongly emitted in #304
464
+ broken = touches(params, peer_dir)
465
+ orphans = broken.reads - (broken.writes | spec_topics(peer_dir))
466
+
467
+ assert orphans == {f"{params['irt_1_topic']}/paced"}