rosotacom-dev 2.5.dev61__tar.gz → 2.5.dev62__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 (388) hide show
  1. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/cli.py +219 -3
  3. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/cli_benchmark.py +1 -0
  4. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  5. rosotacom_dev-2.5.dev62/src/rosotacom_dev.egg-info/scm_version.json +8 -0
  6. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_packaged_resources.py +33 -0
  7. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_cli.py +13 -0
  8. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_concurrency.py +147 -0
  9. rosotacom_dev-2.5.dev61/src/rosotacom_dev.egg-info/scm_version.json +0 -8
  10. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.devcontainer/devcontainer.json.template +0 -0
  11. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  12. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/CODEOWNERS +0 -0
  13. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  14. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  15. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  16. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  17. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  18. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  19. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  20. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  21. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  22. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/actions/setup-just/action.yml +0 -0
  23. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/dependabot.yml +0 -0
  24. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/benchmark-calibrate.yml +0 -0
  25. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/benchmark-gate.yml +0 -0
  26. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/dev-release.yml +0 -0
  27. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/image-scan.yml +0 -0
  28. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/nightly-e2e.yml +0 -0
  29. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/pr-lightweight.yml +0 -0
  30. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/pr-merge-gate.yml +0 -0
  31. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.github/workflows/release.yml +0 -0
  32. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/.pre-commit-config.yaml +0 -0
  33. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/AGENTS.md +0 -0
  34. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/CONCURRENCY.md +0 -0
  35. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/CONTRIBUTING.md +0 -0
  36. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/DEVELOPMENT_PRINCIPLES.md +0 -0
  37. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/LICENSE.txt +0 -0
  38. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/MANIFEST.in +0 -0
  39. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/README.md +0 -0
  40. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/budgets.jsonl +0 -0
  41. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/calibration/README.md +0 -0
  42. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/calibration/capacity-size-tight-cyclone.json +0 -0
  43. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  44. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/calibration/probe-loss-nominal-cyclone.json +0 -0
  45. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/calibration/probe-loss-tight-cyclone.json +0 -0
  46. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/calibration/probe-loss-tight-fastdds.json +0 -0
  47. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/calibration/probe-loss-tight-zenoh.json +0 -0
  48. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/codecov.yml +0 -0
  49. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/deployment-configuration.md +0 -0
  50. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/benchmark-ab.md +0 -0
  51. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/benchmark-bag-as-load.md +0 -0
  52. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/ci.md +0 -0
  53. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/ffmpeg-keyframes.md +0 -0
  54. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/README.md +0 -0
  55. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/a-transport-element-doubles-every-fastdds-sample.md +0 -0
  56. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  57. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/delay-alone-no-loss.md +0 -0
  58. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/fastdds-datagram-cap-drops-large-samples.md +0 -0
  59. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/graph-derived-types-block-a-non-graph-transport.md +0 -0
  60. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/head-of-line-irregularity.md +0 -0
  61. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  62. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/keyframe-loss-kills-the-next-deltas.md +0 -0
  63. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/lifespan-is-enforced-by-one-reader-and-not-the-other.md +0 -0
  64. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/lighter-message-loss.md +0 -0
  65. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/oversubscription-queues-not-losses.md +0 -0
  66. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/reorder-becomes-reader-loss.md +0 -0
  67. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/repro/decode_chain_repro.py +0 -0
  68. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/repro/reorder_drop_repro.py +0 -0
  69. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/sender-rows-make-a-dead-link-look-loss-free.md +0 -0
  70. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  71. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/forensics-report.md +0 -0
  72. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/geo-link-map.md +0 -0
  73. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/github-repository-settings.md +0 -0
  74. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/link-bandwidth.md +0 -0
  75. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/link-trace.md +0 -0
  76. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/owner-runbook.md +0 -0
  77. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/performance-bands.md +0 -0
  78. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/playbooks/README.md +0 -0
  79. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/quality-model.md +0 -0
  80. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/release-notes/TEMPLATE.md +0 -0
  81. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/release-notes/v2.1.md +0 -0
  82. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/release-notes/v2.2.md +0 -0
  83. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/release-notes/v2.3.md +0 -0
  84. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/release-notes/v2.4.md +0 -0
  85. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/release.md +0 -0
  86. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  87. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/0002-expectation-concepts.md +0 -0
  88. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/0003-metric-backbone.md +0 -0
  89. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/0004-network-profiles.md +0 -0
  90. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  91. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/0006-dynamic-qos.md +0 -0
  92. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/0007-regression-gate.md +0 -0
  93. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/rfcs/README.md +0 -0
  94. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/testing.md +0 -0
  95. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/docs/work-items.md +0 -0
  96. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/install.sh +0 -0
  97. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/justfile +0 -0
  98. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/media/control_center.png +0 -0
  99. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/media/echo_test.png +0 -0
  100. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/media/forensics-report-example.png +0 -0
  101. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/media/host.png +0 -0
  102. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/media/vehicle.png +0 -0
  103. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/pyproject.toml +0 -0
  104. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/requirements.txt +0 -0
  105. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/rosotacom.yaml +0 -0
  106. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/session-configuration.md +0 -0
  107. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/setup.cfg +0 -0
  108. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/__init__.py +0 -0
  109. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/__main__.py +0 -0
  110. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/anonymize.py +0 -0
  111. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/bag_ground_truth.py +0 -0
  112. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/benched_set.py +0 -0
  113. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/benchmark.py +0 -0
  114. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/benchmark_delta.py +0 -0
  115. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/bundle_check.py +0 -0
  116. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/deployment.py +0 -0
  117. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/ffmpeg_packet.py +0 -0
  118. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/forensics.py +0 -0
  119. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/geomap.py +0 -0
  120. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/image_cache.py +0 -0
  121. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/netem_dist.py +0 -0
  122. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/network_preflight.py +0 -0
  123. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/network_profiles.py +0 -0
  124. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/network_shaper.py +0 -0
  125. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/plots.py +0 -0
  126. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/py.typed +0 -0
  127. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/benched-set.yaml +0 -0
  128. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/.gitignore +0 -0
  129. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/README.md +0 -0
  130. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/__init__.py +0 -0
  131. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  132. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  133. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
  134. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/profiles.yaml +0 -0
  135. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  136. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/ros2docker.lyrical.json +0 -0
  137. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  138. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  139. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  140. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  141. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.lyrical.ros2docker.json +0 -0
  142. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  143. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  144. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  145. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  146. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.lyrical.ros2docker.json +0 -0
  147. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  148. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  149. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  150. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  151. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  152. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  153. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  154. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  155. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  156. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  157. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  158. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  159. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  160. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  161. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  162. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  163. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  164. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  165. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/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.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  167. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  168. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  169. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  170. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  171. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  172. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  173. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  174. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  175. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  176. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  177. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  178. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  179. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  180. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  181. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  182. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  183. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  184. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  185. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  186. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  187. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  188. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  189. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  190. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  191. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  192. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  193. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  195. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  196. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  198. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  199. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  200. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  201. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  202. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  203. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  204. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  205. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  206. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  207. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  208. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  209. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ros2docker.json.example +0 -0
  210. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ros2docker.lyrical.json.example +0 -0
  211. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  212. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  213. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  214. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  215. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  216. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  217. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  218. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  219. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  220. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  221. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  222. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  223. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/fastdds_easy_mode.xml.template +0 -0
  224. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/fastdds_local.xml.template +0 -0
  225. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/fastdds_tuned.xml.template +0 -0
  226. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  227. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
  228. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  229. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  230. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  231. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  232. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  233. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  234. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  235. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  236. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  237. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  238. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  239. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  240. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  241. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  242. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  243. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  244. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  245. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  246. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  247. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  248. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  249. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  250. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
  251. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  252. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  253. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  254. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  255. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  256. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  257. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  258. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  259. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  260. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  261. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  262. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  263. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  264. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  265. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  266. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  267. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  268. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  269. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
  270. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
  271. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  272. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  273. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  274. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  275. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  276. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  277. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  278. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  279. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
  280. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
  281. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  282. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  283. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  284. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  285. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  286. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  287. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  288. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  289. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  290. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  291. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  292. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
  293. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  294. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  295. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  296. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
  297. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  298. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  299. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  300. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  301. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  302. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
  303. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  304. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  305. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  306. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/status_eval.py +0 -0
  307. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/stream_stats.py +0 -0
  308. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/trace_profiles.py +0 -0
  309. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/transit.py +0 -0
  310. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom/video_quality.py +0 -0
  311. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom_dev.egg-info/SOURCES.txt +0 -0
  312. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  313. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  314. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  315. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom_dev.egg-info/scm_file_list.json +0 -0
  316. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  317. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/terminology.md +0 -0
  318. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_benched_set_registry.py +0 -0
  319. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_findings.py +0 -0
  320. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_generated_topics_have_a_producer.py +0 -0
  321. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_lyrical_runtime_compat.py +0 -0
  322. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_markdown_links.py +0 -0
  323. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_ota_source_bundle.py +0 -0
  324. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_package_metadata.py +0 -0
  325. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  326. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_pytest_policy.py +0 -0
  327. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_readme_examples.py +0 -0
  328. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_security_maintenance_config.py +0 -0
  329. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_session_configuration_doc.py +0 -0
  330. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_session_template_substitution.py +0 -0
  331. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/contract/test_workflow_contracts.py +0 -0
  332. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/conftest.py +0 -0
  333. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_anonymize_e2e.py +0 -0
  334. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_benchmark_ab.py +0 -0
  335. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_benchmark_capacity.py +0 -0
  336. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_benchmark_replay.py +0 -0
  337. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_parallel_smoke.py +0 -0
  338. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_smoke.py +0 -0
  339. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_timeline_stepping.py +0 -0
  340. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/e2e/test_video_quality_e2e.py +0 -0
  341. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/sessions/generate_rmw_matrix.py +0 -0
  342. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  343. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  344. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  345. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  346. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/sessions/rmw_matrix/1_heartbeat_fastdds-default/session-definition.yaml +0 -0
  347. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  348. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_anonymize.py +0 -0
  349. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_bag_ground_truth.py +0 -0
  350. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_benched_set.py +0 -0
  351. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_benchmark.py +0 -0
  352. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_benchmark_delta.py +0 -0
  353. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_bundle_check.py +0 -0
  354. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_catmux_pane_logging.py +0 -0
  355. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_cli_benchmark.py +0 -0
  356. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_container_shaping.py +0 -0
  357. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_deployment.py +0 -0
  358. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_ffmpeg_flags.py +0 -0
  359. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_ffmpeg_packet.py +0 -0
  360. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_field_profiles.py +0 -0
  361. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_forensics.py +0 -0
  362. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_geomap.py +0 -0
  363. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_image_cache.py +0 -0
  364. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_link_bytes.py +0 -0
  365. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_link_trace.py +0 -0
  366. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_netem_dist.py +0 -0
  367. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_network_preflight.py +0 -0
  368. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_network_profiles.py +0 -0
  369. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_network_shaper.py +0 -0
  370. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_ota_link_configs.py +0 -0
  371. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_ota_unwrapper.py +0 -0
  372. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_ota_wrapper.py +0 -0
  373. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_ota_xml.py +0 -0
  374. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_playout_pacer.py +0 -0
  375. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_plots.py +0 -0
  376. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_pub_sub_pair_types.py +0 -0
  377. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_qos_fallback_logging.py +0 -0
  378. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_relay_optional_parameters.py +0 -0
  379. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_ros_node_entity_ownership.py +0 -0
  380. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_session_pipeline.py +0 -0
  381. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_stage_latency.py +0 -0
  382. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_status_eval.py +0 -0
  383. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_status_overview.py +0 -0
  384. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_status_overview_observer.py +0 -0
  385. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_stream_stats.py +0 -0
  386. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_trace_profiles.py +0 -0
  387. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/tests/unit/test_transit.py +0 -0
  388. {rosotacom_dev-2.5.dev61 → rosotacom_dev-2.5.dev62}/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.dev61
3
+ Version: 2.5.dev62
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -275,6 +275,13 @@ class OtaSmokePeer:
275
275
  #: list. Naming the transport lets the narrow thing be substituted without
276
276
  #: this file having to know what narrow means.
277
277
  exec_command: str | None = None
278
+ #: CPUs this peer's session containers are confined to, e.g. "0-3".
279
+ #:
280
+ #: The reason it is per peer: a reservation is a property of one machine's
281
+ #: contention, not of the run. A bench pair is usually one busy workstation
282
+ #: shared with somebody else and one idle machine, and pinning the idle one
283
+ #: would only take capacity away from the measurement.
284
+ cpuset: str | None = None
278
285
 
279
286
 
280
287
  @dataclass(frozen=True)
@@ -1189,6 +1196,11 @@ def _parse_peer_exec_overrides(overrides: list[str] | None) -> dict[str, str]:
1189
1196
  return parse_assignments(overrides, option="--peer-exec")
1190
1197
 
1191
1198
 
1199
+ def _parse_peer_cpuset_overrides(overrides: list[str] | None) -> dict[str, str]:
1200
+ parsed = parse_assignments(overrides, option="--peer-cpuset")
1201
+ return {name: validate_cpuset(value) for name, value in parsed.items()}
1202
+
1203
+
1192
1204
  def _deployment_config(runtime: RuntimeConfig) -> DeploymentConfig | None:
1193
1205
  return load_deployment(runtime.deployment)
1194
1206
 
@@ -1749,8 +1761,13 @@ def _ota_plan_from_bindings(
1749
1761
  checkouts: Mapping[str, str] | None = None,
1750
1762
  project: str | None = None,
1751
1763
  exec_commands: Mapping[str, str] | None = None,
1764
+ cpusets: Mapping[str, str] | None = None,
1752
1765
  ) -> OtaSmokePlan:
1753
1766
  _ota_validate_install_mode(install_mode)
1767
+ if cpusets:
1768
+ unknown = sorted(set(cpusets) - set(bindings))
1769
+ if unknown:
1770
+ raise RuntimeError(f"--peer-cpuset names a peer the session does not have: {', '.join(unknown)}")
1754
1771
  if exec_commands:
1755
1772
  unknown = sorted(set(exec_commands) - set(bindings))
1756
1773
  if unknown:
@@ -1811,6 +1828,7 @@ def _ota_plan_from_bindings(
1811
1828
  address=binding.address,
1812
1829
  checkout=(checkouts or {}).get(name) if install_mode == "checkout" else None,
1813
1830
  exec_command=(exec_commands or {}).get(name),
1831
+ cpuset=(cpusets or {}).get(name),
1814
1832
  )
1815
1833
  for name, binding in bindings.items()
1816
1834
  }
@@ -1839,6 +1857,7 @@ def _ota_write_state(instance: SessionInstance, plan: OtaSmokePlan) -> OtaSmokeP
1839
1857
  "address": peer.address,
1840
1858
  "ssh": peer.ssh,
1841
1859
  "checkout": peer.checkout,
1860
+ "cpuset": peer.cpuset,
1842
1861
  "exec": peer.exec_command,
1843
1862
  }
1844
1863
  for name, peer in sorted(plan.peers.items())
@@ -1865,6 +1884,7 @@ def _ota_load_state(path_arg: str) -> OtaSmokePlan:
1865
1884
  address=str(peer_raw["address"]),
1866
1885
  ssh=str(peer_raw["ssh"]) if peer_raw.get("ssh") else None,
1867
1886
  checkout=str(peer_raw["checkout"]) if peer_raw.get("checkout") else None,
1887
+ cpuset=str(peer_raw["cpuset"]) if peer_raw.get("cpuset") else None,
1868
1888
  exec_command=str(peer_raw["exec"]) if peer_raw.get("exec") else None,
1869
1889
  )
1870
1890
  install_pin = raw.get("install_pin")
@@ -2199,6 +2219,7 @@ def _ota_start_parts(
2199
2219
  mode: str,
2200
2220
  force: bool = True,
2201
2221
  link_trace_parts: list[str] | None = None,
2222
+ cpuset: str | None = None,
2202
2223
  ) -> list[str]:
2203
2224
  if target.target_type == "scenario":
2204
2225
  parts = ["scenario", "start", target.name, "--identity", identity, "--mode", mode, "--instance-id", instance_id]
@@ -2215,6 +2236,8 @@ def _ota_start_parts(
2215
2236
  ]
2216
2237
  parts.append("--force" if force else "--no-force")
2217
2238
  parts.extend(link_trace_parts or [])
2239
+ if cpuset:
2240
+ parts.extend(["--cpuset", cpuset])
2218
2241
  for override in peer_args:
2219
2242
  parts.extend(["--peer-address", override])
2220
2243
  return parts
@@ -2229,6 +2252,7 @@ def _ota_communication_start_parts(
2229
2252
  mode: str,
2230
2253
  force: bool = True,
2231
2254
  link_trace_parts: list[str] | None = None,
2255
+ cpuset: str | None = None,
2232
2256
  ) -> list[str]:
2233
2257
  parts = [
2234
2258
  "start",
@@ -2243,6 +2267,8 @@ def _ota_communication_start_parts(
2243
2267
  ]
2244
2268
  parts.append("--force" if force else "--no-force")
2245
2269
  parts.extend(link_trace_parts or [])
2270
+ if cpuset:
2271
+ parts.extend(["--cpuset", cpuset])
2246
2272
  for override in peer_args:
2247
2273
  parts.extend(["--peer-address", override])
2248
2274
  return parts
@@ -2658,6 +2684,7 @@ def _resolve_ota_smoke_context(
2658
2684
  checkouts=checkouts,
2659
2685
  project=project,
2660
2686
  exec_commands=exec_commands,
2687
+ cpusets=_parse_peer_cpuset_overrides(getattr(args, "peer_cpuset", None)),
2661
2688
  )
2662
2689
  plan_peers = set(plan.peers)
2663
2690
  target_peers = set(_peer_keys_from_cfg(target.cfg))
@@ -2877,10 +2904,121 @@ def _ota_conflict_check(plan: OtaSmokePlan, *, dry_run: bool) -> None:
2877
2904
  _LOAD_WARN_PER_CPU = 0.7
2878
2905
  _LOAD_REFUSE_PER_CPU = 2.0
2879
2906
 
2907
+ # Thresholds for a peer whose containers are pinned to a CPU set. These are a
2908
+ # different quantity from the ones above and deliberately not the same numbers:
2909
+ # load-per-CPU counts runnable threads and is unbounded, while this is the busy
2910
+ # fraction of the pinned CPUs and cannot exceed 1.0. What it answers is the only
2911
+ # question a reservation raises — is anybody else on the cores this run was
2912
+ # given — so it is read before the run puts its own work there.
2913
+ _CPUSET_WARN_BUSY = 0.5
2914
+ _CPUSET_REFUSE_BUSY = 0.85
2915
+
2916
+
2917
+ #: Two snapshots of the per-CPU jiffy counters, a second apart. A busy fraction
2918
+ #: needs a delta -- the absolute counters describe the machine since boot, which
2919
+ #: says nothing about who is on those cores now.
2920
+ _CPU_BUSY_PROBE = "grep '^cpu[0-9]' /proc/stat; sleep 1; echo ---; grep '^cpu[0-9]' /proc/stat"
2921
+
2922
+
2923
+ def _parse_cpu_busy(stdout: str, cpus: Sequence[int]) -> dict[int, float] | None:
2924
+ """Busy fraction per CPU between the two /proc/stat snapshots.
2925
+
2926
+ Busy is everything but idle and iowait: a core waiting on disk is not
2927
+ competing for the runqueue this measurement cares about, and counting it
2928
+ would refuse runs on a machine that is merely writing a bag.
2929
+ """
2930
+ before, sep, after = stdout.partition("---")
2931
+ if not sep:
2932
+ return None
2933
+
2934
+ def snapshot(text: str) -> dict[int, tuple[int, int]]:
2935
+ out: dict[int, tuple[int, int]] = {}
2936
+ for line in text.splitlines():
2937
+ fields = line.split()
2938
+ if len(fields) < 6 or not fields[0].startswith("cpu") or not fields[0][3:].isdigit():
2939
+ continue
2940
+ try:
2941
+ values = [int(x) for x in fields[1:]]
2942
+ except ValueError:
2943
+ continue
2944
+ total = sum(values)
2945
+ idle = values[3] + values[4] # idle + iowait
2946
+ out[int(fields[0][3:])] = (total, idle)
2947
+ return out
2948
+
2949
+ first, second = snapshot(before), snapshot(after)
2950
+ busy: dict[int, float] = {}
2951
+ for cpu in cpus:
2952
+ if cpu not in first or cpu not in second:
2953
+ return None
2954
+ total_delta = second[cpu][0] - first[cpu][0]
2955
+ idle_delta = second[cpu][1] - first[cpu][1]
2956
+ if total_delta <= 0:
2957
+ return None
2958
+ busy[cpu] = max(0.0, min(1.0, 1.0 - idle_delta / total_delta))
2959
+ return busy or None
2960
+
2961
+
2962
+ def _ota_cpuset_load_check(peer: OtaSmokePeer, *, dry_run: bool) -> bool:
2963
+ """Judge the CPUs this peer is pinned to, not the machine around them.
2964
+
2965
+ A reservation makes a machine measurable without making it quiet: the other
2966
+ account keeps every thread it had, they just queue on fewer cores, so
2967
+ /proc/loadavg reads higher than before and says nothing about whether the
2968
+ reserved cores are free. Reading those cores directly is the only way the
2969
+ preflight can tell a partitioned workstation from a saturated one.
2970
+
2971
+ Returns False when the probe could not produce a number, so the caller can
2972
+ fall back to the machine-wide check rather than silently skip it.
2973
+ """
2974
+ if not peer.cpuset:
2975
+ return False
2976
+ cpus = parse_cpuset_list(peer.cpuset)
2977
+ result = _ota_run(
2978
+ peer,
2979
+ _CPU_BUSY_PROBE,
2980
+ label=f"{peer.name}: load on CPUs {peer.cpuset}",
2981
+ dry_run=dry_run,
2982
+ batch=True,
2983
+ check=False,
2984
+ )
2985
+ if dry_run:
2986
+ return True
2987
+ if result.returncode != 0:
2988
+ return False
2989
+ busy = _parse_cpu_busy(result.stdout or "", cpus)
2990
+ if busy is None:
2991
+ return False
2992
+
2993
+ worst_cpu = max(busy, key=lambda cpu: busy[cpu])
2994
+ mean = sum(busy.values()) / len(busy)
2995
+ detail = " ".join(f"cpu{cpu}={busy[cpu] * 100:.0f}%" for cpu in sorted(busy))
2996
+ summary = (
2997
+ f"CPUs {peer.cpuset} are {mean * 100:.0f}% busy on average "
2998
+ f"(worst cpu{worst_cpu} at {busy[worst_cpu] * 100:.0f}%) — {detail}"
2999
+ )
3000
+ if mean >= _CPUSET_REFUSE_BUSY:
3001
+ raise RuntimeError(
3002
+ f"Peer {peer.name} is pinned to CPUs {peer.cpuset}, and they are not free: {summary}.\n"
3003
+ "The reservation is either not in force or somebody else is inside it. Check it on that "
3004
+ "machine, pin to different CPUs, or — if this load is yours and expected — pass "
3005
+ "--skip-conflict-check."
3006
+ )
3007
+ if mean >= _CPUSET_WARN_BUSY:
3008
+ print(f"! {peer.name}: {summary} — the run has less of its reservation than it looks")
3009
+ else:
3010
+ print(f"+ {peer.name}: {summary}")
3011
+ return True
3012
+
2880
3013
 
2881
3014
  def _ota_load_check(plan: OtaSmokePlan, *, dry_run: bool) -> None:
2882
3015
  """Refuse to measure on a peer that is busy with someone else's work."""
2883
3016
  for peer in plan.peers.values():
3017
+ # A pinned peer is judged on its own CPUs. The machine-wide figure is the
3018
+ # wrong question there: it is high by construction on exactly the shared
3019
+ # workstation the pinning exists for.
3020
+ if _ota_cpuset_load_check(peer, dry_run=dry_run):
3021
+ continue
2884
3022
  result = _ota_run(
2885
3023
  peer,
2886
3024
  "cut -d' ' -f1-3 /proc/loadavg; nproc",
@@ -2910,7 +3048,9 @@ def _ota_load_check(plan: OtaSmokePlan, *, dry_run: bool) -> None:
2910
3048
  f"Peer {peer.name} is carrying unrelated load: {summary}.\n"
2911
3049
  "A measurement taken here would describe the contention, not the link.\n"
2912
3050
  "Wait for the machine to go quiet, measure on another pair, or — if this "
2913
- "load is yours and expected — pass --skip-conflict-check."
3051
+ "load is yours and expected — pass --skip-conflict-check.\n"
3052
+ "If those CPUs were reserved away from another account, pin this peer to them "
3053
+ "with --peer-cpuset and the check will read them instead of the whole machine."
2914
3054
  )
2915
3055
  if ratio >= _LOAD_WARN_PER_CPU:
2916
3056
  print(f"! {peer.name}: {summary} — comparisons survive if runs are interleaved, absolute latencies do not")
@@ -3692,7 +3832,15 @@ def _ota_start_peers(
3692
3832
  peer = plan.peers[peer_name]
3693
3833
  command = _ota_rosotacom_command(
3694
3834
  plan,
3695
- _ota_start_parts(target, peer_name, instance_id, peer_args, mode=mode, link_trace_parts=link_trace_parts),
3835
+ _ota_start_parts(
3836
+ target,
3837
+ peer_name,
3838
+ instance_id,
3839
+ peer_args,
3840
+ mode=mode,
3841
+ link_trace_parts=link_trace_parts,
3842
+ cpuset=peer.cpuset,
3843
+ ),
3696
3844
  peer,
3697
3845
  )
3698
3846
  _ota_run(peer, command, label=f"{peer_name}: start {target.name}", dry_run=dry_run)
@@ -4089,6 +4237,7 @@ def _ota_create_tmux(
4089
4237
  peer_args,
4090
4238
  mode="attach",
4091
4239
  link_trace_parts=link_trace_parts,
4240
+ cpuset=first_peer.cpuset,
4092
4241
  ),
4093
4242
  first_peer,
4094
4243
  )
@@ -4169,6 +4318,7 @@ def _ota_create_tmux(
4169
4318
  peer_args,
4170
4319
  mode="attach",
4171
4320
  link_trace_parts=link_trace_parts,
4321
+ cpuset=peer.cpuset,
4172
4322
  ),
4173
4323
  peer,
4174
4324
  )
@@ -5235,6 +5385,43 @@ def _local_ipc_run_args(network_isolated: bool) -> list[str]:
5235
5385
  return [] if network_isolated else ["--ipc", "host"]
5236
5386
 
5237
5387
 
5388
+ _CPUSET_RE = re.compile(r"^\d+(-\d+)?(,\d+(-\d+)?)*$")
5389
+
5390
+
5391
+ def validate_cpuset(cpuset: str) -> str:
5392
+ """Accept a docker `--cpuset-cpus` list, and nothing else.
5393
+
5394
+ This string is handed to `docker run` and travels to a peer over ssh, so it
5395
+ is validated rather than trusted: only decimal CPU numbers and ranges, the
5396
+ exact grammar docker documents. Anything else — a shell metacharacter, a
5397
+ negative number, an empty element — is a caller error and says so here
5398
+ instead of at the far end of an ssh pipe.
5399
+ """
5400
+ cleaned = cpuset.strip()
5401
+ if not _CPUSET_RE.match(cleaned):
5402
+ raise RuntimeError(
5403
+ f"--cpuset {cpuset!r} is not a CPU list: expected decimal numbers and ranges like '0-3' or '0,2,4-7'."
5404
+ )
5405
+ for part in cleaned.split(","):
5406
+ if "-" in part:
5407
+ low, high = (int(x) for x in part.split("-", 1))
5408
+ if high < low:
5409
+ raise RuntimeError(f"--cpuset {cpuset!r}: range {part!r} counts backwards.")
5410
+ return cleaned
5411
+
5412
+
5413
+ def parse_cpuset_list(cpuset: str) -> list[int]:
5414
+ """Expand a validated CPU list into the individual CPU numbers."""
5415
+ cpus: list[int] = []
5416
+ for part in validate_cpuset(cpuset).split(","):
5417
+ if "-" in part:
5418
+ low, high = (int(x) for x in part.split("-", 1))
5419
+ cpus.extend(range(low, high + 1))
5420
+ else:
5421
+ cpus.append(int(part))
5422
+ return sorted(set(cpus))
5423
+
5424
+
5238
5425
  def _base_extra_run_args(
5239
5426
  runtime: RuntimeConfig,
5240
5427
  session: ResolvedSession,
@@ -5242,8 +5429,15 @@ def _base_extra_run_args(
5242
5429
  instance: SessionInstance,
5243
5430
  *,
5244
5431
  network_isolated: bool = False,
5432
+ cpuset: str | None = None,
5245
5433
  ) -> list[str]:
5246
5434
  args: list[str] = _local_ipc_run_args(network_isolated)
5435
+ if cpuset:
5436
+ # Confine every container of this session to the given CPUs. On a shared
5437
+ # workstation the point is not to slow this session down: it is to put it
5438
+ # on the cores that were reserved away from whoever else is on the
5439
+ # machine, so a latency measurement stops competing for a runqueue.
5440
+ args.extend(["--cpuset-cpus", validate_cpuset(cpuset)])
5247
5441
  ros2docker_cfg = load_config(runtime.ros2docker_config)
5248
5442
 
5249
5443
  if not ros2docker_cfg.get("mount_ws"):
@@ -5427,7 +5621,9 @@ def start_session(args: argparse.Namespace) -> str:
5427
5621
  " (or pass --force to replace it, --skip-conflict-check to ignore it).",
5428
5622
  )
5429
5623
  image_name = _scoped_image_name(runtime)
5430
- extra_run_args = _base_extra_run_args(runtime, session, cfg, instance, network_isolated=network_isolated)
5624
+ extra_run_args = _base_extra_run_args(
5625
+ runtime, session, cfg, instance, network_isolated=network_isolated, cpuset=getattr(args, "cpuset", None)
5626
+ )
5431
5627
  run_override: dict[str, object] = {}
5432
5628
  network_name = getattr(args, "network_name", None)
5433
5629
  if network_name:
@@ -8912,6 +9108,18 @@ def _add_ota_install_args(parser: argparse.ArgumentParser) -> None:
8912
9108
  "Required for every peer in that mode; the same repository is at a different path on each machine."
8913
9109
  ),
8914
9110
  )
9111
+ parser.add_argument(
9112
+ "--peer-cpuset",
9113
+ action="append",
9114
+ default=[],
9115
+ metavar="PEER=CPUS",
9116
+ help=(
9117
+ "Confine this peer's session containers to these CPUs, e.g. 'a=0-3'. For a shared "
9118
+ "workstation whose CPUs were reserved away from another account: the run then measures "
9119
+ "on cores nobody else can take, and the load preflight judges those cores instead of "
9120
+ "the whole machine."
9121
+ ),
9122
+ )
8915
9123
  parser.add_argument(
8916
9124
  "--peer-exec",
8917
9125
  action="append",
@@ -8980,6 +9188,14 @@ def _add_start_args(parser: argparse.ArgumentParser) -> None:
8980
9188
  help="Require three bounded ICMP probes to the peer to succeed before starting.",
8981
9189
  )
8982
9190
  parser.add_argument("--instance-id", help="Join or create a named runtime session instance.")
9191
+ parser.add_argument(
9192
+ "--cpuset",
9193
+ help=(
9194
+ "Confine this session's containers to these CPUs, e.g. '0-3'. Use on a shared "
9195
+ "workstation where those CPUs were reserved away from another account: the "
9196
+ "session then measures on cores nobody else can take."
9197
+ ),
9198
+ )
8983
9199
  parser.add_argument(
8984
9200
  "--skip-conflict-check",
8985
9201
  action="store_true",
@@ -5214,6 +5214,7 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
5214
5214
  peer_address=getattr(args, "peer_address", None),
5215
5215
  peer_ssh=getattr(args, "peer_ssh", None),
5216
5216
  peer_checkout=getattr(args, "peer_checkout", None),
5217
+ peer_cpuset=getattr(args, "peer_cpuset", None),
5217
5218
  peer_exec=getattr(args, "peer_exec", None),
5218
5219
  install_mode=getattr(args, "install_mode", None),
5219
5220
  install_pin=getattr(args, "install_pin", None),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev61
3
+ Version: 2.5.dev62
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.4",
3
+ "distance": 62,
4
+ "node": "gfec674ece7749d5ecb7bf4b65d12faa17e8f965e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-08-20"
8
+ }
@@ -537,3 +537,36 @@ def test_every_default_ota_profile_template_is_packaged() -> None:
537
537
  defaults = list(gsf._DDS_OTA_DEFAULT_CONFIG.values()) + list(gsf._DDS_LOCAL_DEFAULT_CONFIG.values())
538
538
  missing = [name for name in defaults if name and not (ota_configs / f"{name}.template").exists()]
539
539
  assert not missing, f"default profile templates absent from the package: {missing}"
540
+
541
+
542
+ def test_benchmark_forwards_every_shared_ota_peer_option() -> None:
543
+ """A hand-copied field list drops new options silently, and did.
544
+
545
+ `cli_benchmark` builds an `argparse.Namespace` for the OTA layer by naming
546
+ each field it forwards. `--peer-cpuset` was added to the shared OTA argument
547
+ group, reached the parser, and was accepted on the command line — and then
548
+ never arrived, because this list did not mention it. Every unit test passed
549
+ and the feature was inert on the machine it was built for.
550
+
551
+ So the list is checked against the parser rather than maintained by hand:
552
+ whatever `_add_ota_install_args` defines has to appear here too.
553
+ """
554
+ import argparse
555
+ import inspect
556
+
557
+ import rosotacom.cli as cli
558
+ import rosotacom.cli_benchmark as cli_benchmark
559
+
560
+ parser = argparse.ArgumentParser()
561
+ cli._add_ota_install_args(parser)
562
+ peer_dests = {
563
+ action.dest for action in parser._actions if action.dest.startswith("peer_") and action.dest != "help"
564
+ }
565
+ assert peer_dests, "the shared OTA argument group defines no --peer-* options"
566
+
567
+ forwarded = inspect.getsource(cli_benchmark)
568
+ missing = sorted(dest for dest in peer_dests if f"{dest}=getattr(args" not in forwarded)
569
+ assert not missing, (
570
+ f"cli_benchmark does not forward {', '.join(missing)} into the OTA namespace, "
571
+ "so the option is accepted on the command line and then ignored"
572
+ )
@@ -2258,6 +2258,19 @@ def test_resolve_session_and_base_extra_run_args(tmp_path: Path, monkeypatch: py
2258
2258
  assert resolved.container_dir == "/session/definitions/1_heartbeat"
2259
2259
  assert f"{rosotacom.WS_DIR.resolve()}:/ws" in args
2260
2260
  assert f"{runtime.session_configs_dir[0]}:/session/definitions:ro" in args
2261
+ assert "--cpuset-cpus" not in args
2262
+
2263
+ # A cpuset confines every container of the session. Without this the CPUs a
2264
+ # shared workstation reserved are merely idle, not used by the run.
2265
+ pinned = rosotacom._base_extra_run_args(runtime, resolved, {"peers": {"a": {}, "b": {}}}, instance, cpuset=" 0-3 ")
2266
+ assert pinned[pinned.index("--cpuset-cpus") + 1] == "0-3"
2267
+
2268
+ # It is validated here too, not only where it is parsed: this is the last
2269
+ # place before the string becomes part of a docker command line.
2270
+ with pytest.raises(RuntimeError):
2271
+ rosotacom._base_extra_run_args(
2272
+ runtime, resolved, {"peers": {"a": {}, "b": {}}}, instance, cpuset="0-3; rm -rf /"
2273
+ )
2261
2274
  assert f"{runtime.session_instances_dir}:/session/instances" in args
2262
2275
  assert "Configured sessions:" in rosotacom._format_available_sessions(runtime)
2263
2276
  # A peer shares the host IPC namespace, so it can exchange local-domain
@@ -796,3 +796,150 @@ def test_the_load_check_does_not_retry(
796
796
  rosotacom._ota_load_check(_load_plan(tmp_path), dry_run=False)
797
797
 
798
798
  assert len(calls) == 1
799
+
800
+
801
+ def test_validate_cpuset_accepts_a_cpu_list_and_nothing_else() -> None:
802
+ assert rosotacom.validate_cpuset("0-3") == "0-3"
803
+ assert rosotacom.validate_cpuset(" 0,2,4-7 ") == "0,2,4-7"
804
+ assert rosotacom.parse_cpuset_list("0-3,8") == [0, 1, 2, 3, 8]
805
+
806
+ # The string reaches `docker run` and travels over ssh, so it is validated
807
+ # rather than trusted.
808
+ for bad in ("0-3; rm -rf /", "$(id)", "0..3", "-1", "", "0-", "a-b"):
809
+ with pytest.raises(RuntimeError):
810
+ rosotacom.validate_cpuset(bad)
811
+ with pytest.raises(RuntimeError) as excinfo:
812
+ rosotacom.validate_cpuset("7-2")
813
+ assert "backwards" in str(excinfo.value)
814
+
815
+
816
+ def test_cpu_busy_is_read_from_two_snapshots() -> None:
817
+ """A busy fraction needs the delta; the absolute counters describe boot onward."""
818
+ stdout = (
819
+ "cpu0 100 0 100 800 0 0 0 0 0 0\n"
820
+ "cpu1 100 0 100 800 0 0 0 0 0 0\n"
821
+ "---\n"
822
+ # cpu0 gains 100 busy and 900 idle -> 10% busy.
823
+ "cpu0 150 0 150 1700 0 0 0 0 0 0\n"
824
+ # cpu1 gains 900 busy and 100 idle -> 90% busy.
825
+ "cpu1 550 0 550 900 0 0 0 0 0 0\n"
826
+ )
827
+ busy = rosotacom._parse_cpu_busy(stdout, [0, 1])
828
+ assert busy is not None
829
+ assert busy[0] == pytest.approx(0.10, abs=0.01)
830
+ assert busy[1] == pytest.approx(0.90, abs=0.01)
831
+
832
+ # iowait does not count as competition for the runqueue: a peer writing a
833
+ # bag must not read as a peer that cannot be measured on.
834
+ io = "cpu0 0 0 0 100 0 0 0 0 0 0\n---\ncpu0 0 0 0 100 900 0 0 0 0 0\n"
835
+ busy_io = rosotacom._parse_cpu_busy(io, [0])
836
+ assert busy_io is not None
837
+ assert busy_io[0] == pytest.approx(0.0, abs=0.01)
838
+
839
+ assert rosotacom._parse_cpu_busy("no separator here", [0]) is None
840
+ assert rosotacom._parse_cpu_busy(stdout, [9]) is None # a CPU the peer does not have
841
+
842
+
843
+ def _pinned_peer(cpuset: str = "0-3") -> rosotacom.OtaSmokePeer:
844
+ return rosotacom.OtaSmokePeer("a", "robot-a", "10.0.0.10", cpuset=cpuset)
845
+
846
+
847
+ def _busy_probe(fraction: float) -> str:
848
+ """Two snapshots in which every CPU is `fraction` busy."""
849
+ lines = [f"cpu{cpu} 0 0 0 0 0 0 0 0 0 0" for cpu in range(4)]
850
+ after = [f"cpu{cpu} {int(1000 * fraction)} 0 0 {int(1000 * (1 - fraction))} 0 0 0 0 0 0" for cpu in range(4)]
851
+ return "\n".join(lines) + "\n---\n" + "\n".join(after) + "\n"
852
+
853
+
854
+ def test_cpuset_check_refuses_when_the_reserved_cpus_are_not_free(
855
+ monkeypatch: pytest.MonkeyPatch,
856
+ ) -> None:
857
+ """A reservation that is not in force must not read as a quiet machine."""
858
+ monkeypatch.setattr(
859
+ rosotacom,
860
+ "_ota_run",
861
+ lambda peer, script, **kw: subprocess.CompletedProcess([script], 0, _busy_probe(0.95), ""),
862
+ )
863
+ with pytest.raises(RuntimeError) as excinfo:
864
+ rosotacom._ota_cpuset_load_check(_pinned_peer(), dry_run=False)
865
+ message = str(excinfo.value)
866
+ assert "not free" in message
867
+ assert "0-3" in message
868
+
869
+
870
+ def test_cpuset_check_passes_on_reserved_cpus_that_are_idle(
871
+ monkeypatch: pytest.MonkeyPatch,
872
+ capsys: pytest.CaptureFixture[str],
873
+ ) -> None:
874
+ monkeypatch.setattr(
875
+ rosotacom,
876
+ "_ota_run",
877
+ lambda peer, script, **kw: subprocess.CompletedProcess([script], 0, _busy_probe(0.02), ""),
878
+ )
879
+ assert rosotacom._ota_cpuset_load_check(_pinned_peer(), dry_run=False) is True
880
+ assert "CPUs 0-3 are 2% busy" in capsys.readouterr().out
881
+
882
+
883
+ def test_a_pinned_peer_is_not_judged_by_the_machine_wide_load(
884
+ tmp_path: Path,
885
+ monkeypatch: pytest.MonkeyPatch,
886
+ ) -> None:
887
+ """The whole point: a reserved workstation reads high and is still measurable.
888
+
889
+ Measured on majestic on 2026-08-19: 68-75 on 32 CPUs with four cores reserved
890
+ away from the account carrying that load. loadavg cannot see the reservation
891
+ — the other account's threads all stayed runnable, they just queue on fewer
892
+ cores — so judging the machine refuses a run the reservation made possible.
893
+ """
894
+ scripts: list[str] = []
895
+
896
+ def fake_ota_run(peer: object, script: str, **kwargs: object) -> subprocess.CompletedProcess[str]:
897
+ scripts.append(script)
898
+ if "/proc/stat" in script:
899
+ return subprocess.CompletedProcess([script], 0, _busy_probe(0.01), "")
900
+ return subprocess.CompletedProcess([script], 0, "75.68 68.49 67.20\n32\n", "")
901
+
902
+ monkeypatch.setattr(rosotacom, "_ota_run", fake_ota_run)
903
+ plan = rosotacom.OtaSmokePlan(
904
+ state_path=tmp_path / "ota-deployment.yaml",
905
+ workdir="/tmp/rosotacom_ota",
906
+ rosotacom="rosotacom",
907
+ project="rosotacom.yaml",
908
+ peers={"a": _pinned_peer()},
909
+ )
910
+
911
+ rosotacom._ota_load_check(plan, dry_run=False) # must not raise
912
+
913
+ assert any("/proc/stat" in s for s in scripts)
914
+ assert not any("loadavg" in s for s in scripts), "a pinned peer must not fall back to loadavg"
915
+
916
+
917
+ def test_an_unpinned_peer_still_uses_the_machine_wide_load(
918
+ tmp_path: Path,
919
+ monkeypatch: pytest.MonkeyPatch,
920
+ ) -> None:
921
+ monkeypatch.setattr(
922
+ rosotacom,
923
+ "_ota_run",
924
+ _fake_ota_run_factory({"/proc/loadavg": "78.24 81.86 81.28\n32\n"}),
925
+ )
926
+ with pytest.raises(RuntimeError) as excinfo:
927
+ rosotacom._ota_load_check(_load_plan(tmp_path), dry_run=False)
928
+ assert "--peer-cpuset" in str(excinfo.value), "the refusal should name the way out"
929
+
930
+
931
+ def test_a_pinned_peer_confines_its_containers(tmp_path: Path) -> None:
932
+ """The reservation only buys anything if the containers actually land on it."""
933
+ target = rosotacom.InteractiveSmokeTarget(
934
+ name="s",
935
+ target_type="session",
936
+ session=rosotacom.ResolvedSession(host_dir=tmp_path, container_dir="/s", source="test"),
937
+ cfg={},
938
+ )
939
+ parts = rosotacom._ota_start_parts(target, "a", "id-1", [], mode="detached", cpuset="0-3")
940
+ assert "--cpuset" in parts
941
+ assert parts[parts.index("--cpuset") + 1] == "0-3"
942
+
943
+ # And without one, nothing is added: the flag must not appear on a peer that
944
+ # was never pinned, or every unpinned run changes shape too.
945
+ assert "--cpuset" not in rosotacom._ota_start_parts(target, "a", "id-1", [], mode="detached")
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "2.4",
3
- "distance": 61,
4
- "node": "gc9f74159413d4d3b06b1e51a67dded0360f1e36b",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-08-19"
8
- }