topicforge 0.6.4__tar.gz → 0.7.1__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 (250) hide show
  1. {topicforge-0.6.4 → topicforge-0.7.1}/CHANGELOG.md +276 -1
  2. {topicforge-0.6.4 → topicforge-0.7.1}/PKG-INFO +27 -20
  3. {topicforge-0.6.4 → topicforge-0.7.1}/README.md +23 -17
  4. {topicforge-0.6.4 → topicforge-0.7.1}/docs/CLIENTS.md +60 -18
  5. topicforge-0.7.1/docs/CONTRACT.md +776 -0
  6. {topicforge-0.6.4 → topicforge-0.7.1}/docs/DDS_QUICKSTART.md +6 -6
  7. topicforge-0.7.1/docs/EVAL.md +158 -0
  8. topicforge-0.7.1/docs/MIGRATION-0.7.md +239 -0
  9. {topicforge-0.6.4 → topicforge-0.7.1}/docs/TESTING.md +4 -4
  10. topicforge-0.7.1/docs/TOOLS.md +459 -0
  11. {topicforge-0.6.4 → topicforge-0.7.1}/docs/TROUBLESHOOTING.md +4 -4
  12. {topicforge-0.6.4 → topicforge-0.7.1}/docs/TUTORIEL.md +2 -2
  13. topicforge-0.7.1/docs/VALIDATION.md +44 -0
  14. {topicforge-0.6.4 → topicforge-0.7.1}/docs/dds-interop-matrix.md +2 -2
  15. {topicforge-0.6.4 → topicforge-0.7.1}/examples/README.md +1 -1
  16. {topicforge-0.6.4 → topicforge-0.7.1}/plugin/README.md +2 -1
  17. {topicforge-0.6.4 → topicforge-0.7.1}/plugin/pyproject.toml +2 -2
  18. {topicforge-0.6.4 → topicforge-0.7.1}/pyproject.toml +7 -6
  19. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/agent_eval/README.md +3 -0
  20. topicforge-0.7.1/scripts/ground_truth/README.md +60 -0
  21. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/__init__.py +1 -1
  22. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/__main__.py +30 -4
  23. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/__init__.py +0 -2
  24. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/base.py +13 -15
  25. topicforge-0.7.1/src/topicforge/adapters/common/bag_kind.py +26 -0
  26. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/cdr_decoder.py +16 -0
  27. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/dds_helpers.py +4 -3
  28. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/endpoints.py +39 -26
  29. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/lifecycle.py +5 -15
  30. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/metrics_buffer.py +11 -11
  31. topicforge-0.7.1/src/topicforge/adapters/common/nodes.py +248 -0
  32. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_endpoints.py +4 -5
  33. topicforge-0.7.1/src/topicforge/adapters/common/qos_fixes.py +518 -0
  34. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_scan.py +58 -15
  35. topicforge-0.7.1/src/topicforge/adapters/common/ros_names.py +31 -0
  36. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/composite.py +23 -8
  37. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_cyclone/adapter.py +27 -15
  38. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_fast/adapter.py +25 -11
  39. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/adapter.py +246 -47
  40. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/echo_parser.py +37 -0
  41. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/echo_stream.py +9 -1
  42. topicforge-0.7.1/src/topicforge/adapters/ros2_live/node_parsers.py +96 -0
  43. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/parsers.py +62 -12
  44. topicforge-0.7.1/src/topicforge/adapters/ros2_live/process_runner.py +141 -0
  45. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/process_tree.py +24 -2
  46. topicforge-0.7.1/src/topicforge/adapters/ros2_live/sample_extras.py +77 -0
  47. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_mock/adapter.py +40 -18
  48. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_mock/fixtures.py +286 -75
  49. topicforge-0.7.1/src/topicforge/adapters/ros2_mock/node_fixtures.py +121 -0
  50. topicforge-0.7.1/src/topicforge/budget.py +50 -0
  51. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/config/settings.py +15 -24
  52. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/constants.py +14 -3
  53. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/models/__init__.py +42 -0
  54. topicforge-0.7.1/src/topicforge/models/nodes.py +158 -0
  55. topicforge-0.7.1/src/topicforge/models/schemas.py +1269 -0
  56. topicforge-0.7.1/src/topicforge/models/summaries.py +242 -0
  57. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/server/app.py +7 -20
  58. topicforge-0.7.1/src/topicforge/server/http.py +75 -0
  59. topicforge-0.7.1/src/topicforge/server/prompts.py +114 -0
  60. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/bag_service.py +51 -15
  61. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/bag_stats.py +2 -0
  62. topicforge-0.7.1/src/topicforge/services/dds_views.py +154 -0
  63. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/factory.py +0 -52
  64. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/health.py +43 -3
  65. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/inspector.py +143 -24
  66. topicforge-0.7.1/src/topicforge/services/summaries/__init__.py +77 -0
  67. topicforge-0.7.1/src/topicforge/services/summaries/_common.py +98 -0
  68. topicforge-0.7.1/src/topicforge/services/summaries/laser_scan.py +143 -0
  69. topicforge-0.7.1/src/topicforge/services/summaries/motion.py +82 -0
  70. topicforge-0.7.1/src/topicforge/services/summaries/rate.py +171 -0
  71. topicforge-0.7.1/src/topicforge/services/summaries/sensor_data.py +113 -0
  72. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/telemetry/client.py +1 -1
  73. topicforge-0.7.1/src/topicforge/tools/guard.py +127 -0
  74. topicforge-0.7.1/src/topicforge/tools/handlers.py +699 -0
  75. topicforge-0.7.1/src/topicforge/tools/schema_compact.py +60 -0
  76. topicforge-0.7.1/tests/contract/_prompts.json +37 -0
  77. topicforge-0.7.1/tests/contract/analyze_bag.json +219 -0
  78. topicforge-0.7.1/tests/contract/detect_qos_mismatches.json +516 -0
  79. topicforge-0.7.1/tests/contract/get_node_info.json +233 -0
  80. topicforge-0.7.1/tests/contract/get_topic_info.json +171 -0
  81. topicforge-0.7.1/tests/contract/health_check.json +303 -0
  82. topicforge-0.7.1/tests/contract/list_endpoints.json +683 -0
  83. topicforge-0.7.1/tests/contract/list_nodes.json +111 -0
  84. topicforge-0.7.1/tests/contract/list_participants.json +298 -0
  85. topicforge-0.7.1/tests/contract/list_topics.json +96 -0
  86. topicforge-0.7.1/tests/contract/participant_events.json +239 -0
  87. topicforge-0.7.1/tests/contract/peek_bag_samples.json +1083 -0
  88. topicforge-0.7.1/tests/contract/peek_dds_samples.json +1077 -0
  89. topicforge-0.7.1/tests/contract/sample_messages.json +1104 -0
  90. topicforge-0.7.1/tests/contract/topic_metrics.json +181 -0
  91. topicforge-0.7.1/tests/fixtures/ground_truth/omnisim_064_results.json +1 -0
  92. topicforge-0.7.1/tests/fixtures/ground_truth/omnisim_064_truth.json +1 -0
  93. topicforge-0.7.1/tests/fixtures/ros2_node/node_info_humble.txt +24 -0
  94. topicforge-0.7.1/tests/fixtures/ros2_node/node_info_no_param_services.txt +13 -0
  95. topicforge-0.7.1/tests/fixtures/ros2_node/node_info_not_found.txt +1 -0
  96. topicforge-0.7.1/tests/fixtures/ros2_node/node_list_duplicates.txt +6 -0
  97. topicforge-0.7.1/tests/fixtures/ros2_node/node_list_plain.txt +2 -0
  98. topicforge-0.7.1/tests/fixtures/ros2_node/node_list_warning_stdout.txt +4 -0
  99. topicforge-0.7.1/tests/fixtures/ros2_node/param_dump_humble.yaml +12 -0
  100. topicforge-0.7.1/tests/fixtures/ros2_node/param_dump_secrets.yaml +13 -0
  101. topicforge-0.7.1/tests/fixtures/ros2_topic_info_verbose_clock.txt +89 -0
  102. {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/Dockerfile +1 -0
  103. topicforge-0.7.1/tests/integration/ros2/__init__.py +0 -0
  104. topicforge-0.7.1/tests/integration/ros2/blocked_node.py +38 -0
  105. topicforge-0.7.1/tests/integration/ros2/entrypoint.sh +75 -0
  106. topicforge-0.7.1/tests/integration/ros2/ground_truth.py +221 -0
  107. {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/publisher.py +18 -17
  108. {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/run_bench.py +12 -0
  109. {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/test_live_adapter.py +147 -5
  110. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_analyze_bag_multi_format.py +12 -15
  111. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_bag_omnisim_humble.py +1 -1
  112. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_bag_service.py +1 -1
  113. topicforge-0.7.1/tests/test_bag_summaries.py +198 -0
  114. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_composite_adapter.py +13 -18
  115. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_config.py +15 -39
  116. topicforge-0.7.1/tests/test_contract_shape.py +73 -0
  117. topicforge-0.7.1/tests/test_contract_snapshots.py +83 -0
  118. topicforge-0.7.1/tests/test_contract_v2.py +179 -0
  119. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_cyclone_adapter.py +1 -1
  120. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_helpers.py +1 -2
  121. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_inactive_reason.py +5 -10
  122. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_schemas.py +8 -8
  123. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_discovery_tracker.py +1 -1
  124. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_endpoints.py +52 -20
  125. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_factory.py +4 -54
  126. topicforge-0.7.1/tests/test_field_names.py +100 -0
  127. topicforge-0.7.1/tests/test_friction_fixes.py +315 -0
  128. topicforge-0.7.1/tests/test_ground_truth_compare.py +737 -0
  129. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_health.py +56 -25
  130. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_history_and_hints.py +1 -3
  131. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_honest_outputs.py +9 -12
  132. topicforge-0.7.1/tests/test_http_transport.py +112 -0
  133. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_inspector.py +15 -11
  134. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_lifecycle_buffer.py +10 -14
  135. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_adapter_graph.py +119 -32
  136. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_adapter_parse.py +2 -2
  137. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_adapter_subprocess.py +34 -15
  138. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_sample_notes.py +8 -7
  139. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_metrics_buffer.py +25 -25
  140. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_mock_adapter.py +47 -18
  141. topicforge-0.7.1/tests/test_node_logic.py +159 -0
  142. topicforge-0.7.1/tests/test_node_parsers.py +135 -0
  143. topicforge-0.7.1/tests/test_node_tools.py +383 -0
  144. topicforge-0.7.1/tests/test_process_runner.py +221 -0
  145. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_process_tree.py +35 -0
  146. topicforge-0.7.1/tests/test_prompts.py +81 -0
  147. topicforge-0.7.1/tests/test_qos_fixes.py +362 -0
  148. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_qos_scan.py +1 -3
  149. topicforge-0.7.1/tests/test_rate_verdicts.py +281 -0
  150. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_sample_options.py +8 -6
  151. topicforge-0.7.1/tests/test_sample_summary_flow.py +376 -0
  152. topicforge-0.7.1/tests/test_server_guard.py +158 -0
  153. topicforge-0.7.1/tests/test_server_info_version.py +35 -0
  154. topicforge-0.7.1/tests/test_server_lanes.py +261 -0
  155. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_stamps.py +1 -1
  156. topicforge-0.7.1/tests/test_summaries.py +408 -0
  157. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_telemetry.py +2 -2
  158. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_tool_annotations.py +7 -7
  159. topicforge-0.7.1/tests/test_tool_descriptions.py +116 -0
  160. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_tools_integration.py +53 -55
  161. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_topic_metrics.py +14 -14
  162. topicforge-0.6.4/docs/VALIDATION.md +0 -15
  163. topicforge-0.6.4/src/topicforge/adapters/dds_dust/__init__.py +0 -10
  164. topicforge-0.6.4/src/topicforge/adapters/dds_dust/adapter.py +0 -106
  165. topicforge-0.6.4/src/topicforge/adapters/dds_opendds/__init__.py +0 -11
  166. topicforge-0.6.4/src/topicforge/adapters/dds_opendds/adapter.py +0 -112
  167. topicforge-0.6.4/src/topicforge/models/schemas.py +0 -1402
  168. topicforge-0.6.4/src/topicforge/tools/handlers.py +0 -700
  169. topicforge-0.6.4/tests/integration/ros2/entrypoint.sh +0 -43
  170. topicforge-0.6.4/tests/test_dust_adapter.py +0 -48
  171. topicforge-0.6.4/tests/test_opendds_adapter.py +0 -60
  172. {topicforge-0.6.4 → topicforge-0.7.1}/.gitignore +0 -0
  173. {topicforge-0.6.4 → topicforge-0.7.1}/LICENSE +0 -0
  174. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/00_hello_pub_sub/README.md +0 -0
  175. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
  176. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/02_why_cant_they_talk/README.md +0 -0
  177. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/03_a_node_crashed/README.md +0 -0
  178. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
  179. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/05_reliability_in_code/README.md +0 -0
  180. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
  181. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/07_deadline_in_code/README.md +0 -0
  182. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
  183. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
  184. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/11_who_talks_to_whom/README.md +0 -0
  185. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
  186. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/13_deadline_not_offered/README.md +0 -0
  187. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/14_restart_loop/README.md +0 -0
  188. {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/README.md +0 -0
  189. {topicforge-0.6.4 → topicforge-0.7.1}/plugin/LICENSE +0 -0
  190. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/README.md +0 -0
  191. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/cyclone_c/README.md +0 -0
  192. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
  193. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
  194. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/dust_py/README.md +0 -0
  195. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
  196. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/fast_py/README.md +0 -0
  197. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
  198. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/rti_c/README.md +0 -0
  199. {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/rti_cpp/README.md +0 -0
  200. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/__init__.py +0 -0
  201. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/dds_introspection.py +0 -0
  202. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/discovery_tracker.py +0 -0
  203. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
  204. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_normalize.py +0 -0
  205. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/stamps.py +0 -0
  206. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/topic_filter.py +0 -0
  207. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/xtypes.py +0 -0
  208. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
  209. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
  210. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
  211. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
  212. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/config/__init__.py +0 -0
  213. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/server/__init__.py +0 -0
  214. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/__init__.py +0 -0
  215. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/sample_budget.py +0 -0
  216. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/telemetry/__init__.py +0 -0
  217. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/tools/__init__.py +0 -0
  218. {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/tools/annotations.py +0 -0
  219. {topicforge-0.6.4 → topicforge-0.7.1}/tests/__init__.py +0 -0
  220. {topicforge-0.6.4 → topicforge-0.7.1}/tests/conftest.py +0 -0
  221. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
  222. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
  223. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
  224. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
  225. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
  226. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
  227. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
  228. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/twist.yaml +0 -0
  229. /topicforge-0.6.4/tests/integration/ros2/__init__.py → /topicforge-0.7.1/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
  230. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
  231. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
  232. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
  233. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
  234. {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
  235. {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/__init__.py +0 -0
  236. {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/test_real_bus.py +0 -0
  237. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_bag_stats.py +0 -0
  238. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_cdr_decoder.py +0 -0
  239. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_cross_vendor.py +0 -0
  240. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_introspection.py +0 -0
  241. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_qos_normalization.py +0 -0
  242. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_echo_parser.py +0 -0
  243. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_echo_stream.py +0 -0
  244. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_example_node_spec.py +0 -0
  245. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_fast_adapter.py +0 -0
  246. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_peek_bag_samples.py +0 -0
  247. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_qos_analyzer.py +0 -0
  248. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_qos_endpoints.py +0 -0
  249. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_version_consistency.py +0 -0
  250. {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_xtypes.py +0 -0
@@ -7,6 +7,279 @@ and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.1] - 2026-10-14
11
+
12
+ QoS fix suggestions and agent-friction fixes from the blind evaluation (`docs/EVAL.md`). Additive only: no field or
13
+ input was renamed, removed or retyped (`docs/CONTRACT.md` section 9).
14
+
15
+ ### Added
16
+
17
+ - `detect_qos_mismatches` fills `suggested_fixes` (output contract 2, additive; see
18
+ `docs/CONTRACT.md` section 3.1): "diagnose, propose the exact fix, verify". Each QoS
19
+ mismatch report and each `partition` split gets up to one fix per side that can change
20
+ (`MismatchReport.suggested_fixes`, and the new `NotMatchedPair.suggested_fixes`), with its
21
+ tradeoff and a `verify_with` call. A ROS 2 topic with a Reliability or Durability finding
22
+ gets a `qos_override` parameter-file entry; every other case gets a `dds_profile` (Fast DDS
23
+ XML plus the cyclonedds-python equivalent, since Cyclone DDS has no XML QoS profile).
24
+ Covered: Reliability, Durability, Deadline, Liveliness, Ownership, LatencyBudget and
25
+ Partition; other findings get a hint instead. Identical findings on a topic share the
26
+ first report's fixes. Text only: TopicForge never applies a fix, and a test fails any
27
+ suggestion containing a stop, remove or write verb.
28
+ - `list_participants` and `participant_events`: `restarts_by_name` (`{name: {lives,
29
+ departures}}` for names seen with several GUIDs or lost) and `restarts_note`, so an agent
30
+ no longer counts GUIDs to find a restart loop.
31
+ - `participant_events`: `age_s` on each event, seconds between the event and the response
32
+ (`null` in mock mode).
33
+ - `detect_qos_mismatches`: `summary`, a one-line reading of the scan that says when pairs are
34
+ not matched and no data flows, even with an empty `reports`.
35
+ - `list_endpoints`: optional input `fields` (`full` default, or `compact`) with the output
36
+ fields `fields` and `qos_summary`; compact replaces each `qos` by a one-line summary and
37
+ drops `type_id`, `announced_ns` and `ros_topic_note`.
38
+
39
+ ### Changed
40
+
41
+ - `tools/list` is about 37 percent smaller (about 178 KB to 113 KB compact), because some
42
+ clients truncated it: tool and field descriptions are shorter, and the generated schema
43
+ titles and nested-model docstrings are no longer served. Names, types, defaults and
44
+ constraints are unchanged. The long explanations moved to `docs/CONTRACT.md` section 9.
45
+ - `domain_id` on `list_participants`, `participant_events`, `topic_metrics` and
46
+ `list_endpoints`: the description now opens with "Ignored", and a non-zero value that
47
+ differs from the domain joined at startup adds an explicit sentence to the response `note`
48
+ (the response `domain_id` already named the observed domain).
49
+
50
+ ### Fixed
51
+
52
+ - `scripts/ground_truth/compare.py` counts only active DDS participants: the `ros2` CLI
53
+ processes that `get_node_info` starts stay listed with status `left` and inflated the count
54
+ (found on the OmniSim kit run of 2026-10-14 on 0.7.0: 76 checks passed, 0 failed).
55
+
56
+ ## [0.7.0] - 2026-10-12
57
+
58
+ Breaking release: output contract 2 (see `docs/MIGRATION-0.7.md`), the move to the MCP SDK
59
+ 2.x, and a tool surface of 14 tools (`list_nodes` and `get_node_info` are new).
60
+
61
+ ### Breaking
62
+
63
+ Output contract 2 (`health_check.contract_version` is 2; 0.6.x is implicitly 1). The tool
64
+ list and the input defaults are unchanged except where noted. `docs/CONTRACT.md` is the
65
+ specification and `docs/MIGRATION-0.7.md` expands this list.
66
+
67
+ One object per tool, never a bare list:
68
+
69
+ - `list_topics`: `list[TopicInfo]` -> `TopicListing {topics, returned, total, truncated,
70
+ mode_effective, note}`.
71
+ - `list_participants`: `list[ParticipantInfo]` -> `ParticipantListing {participants, returned,
72
+ total, truncated, domain_id, mode_effective, note}`.
73
+ - `participant_events`: `list[ParticipantEvent]` -> `ParticipantEventListing {events,
74
+ returned, total, truncated, domain_id, mode_effective, note}`. The silent 200-event cap is
75
+ now `truncated` plus a `note`.
76
+
77
+ Topic models:
78
+
79
+ - `TopicInfo` splits: `list_topics` returns `TopicListItem` (`name`, `message_type`,
80
+ `publisher_count`, `subscriber_count`, no QoS, no `mode_effective`); `get_topic_info`
81
+ returns `TopicInfo`.
82
+ - `TopicInfo.qos_reliability` and `qos_durability` (publishers only) -> `publisher_qos` and
83
+ `subscription_qos` (`{reliability, durability, endpoint_count}`, `mixed` when the endpoints
84
+ of a side disagree), each with a `_note` when it is `null`. A topic with subscribers only
85
+ no longer reports nothing.
86
+ - `TopicInfo` gains `publisher_nodes` and `subscriber_nodes` (fully qualified node names read
87
+ from `ros2 topic info --verbose`) and a `note`.
88
+ - Removed because no adapter ever filled them: `TopicInfo.reader_count`, `writer_count` and
89
+ `qos_profile`; `BagAnalysis.samples_decoded_count`, `participants_recorded` and
90
+ `recording_duration_ns`.
91
+
92
+ Renames (unit suffixes, `_reason` -> `_note`):
93
+
94
+ - `BagAnalysis.duration_seconds` -> `duration_s`.
95
+ - `TopicMetrics.window_seconds` -> `window_s`, `window_seconds_actual` -> `window_actual_s`,
96
+ `frequency_hz_observed` -> `observed_frequency_hz`, `frequency_hz_declared` ->
97
+ `declared_frequency_hz`.
98
+ - Tool inputs: `topic_metrics.window_seconds` -> `window_s`, `participant_events.lookback_seconds`
99
+ -> `lookback_s`.
100
+ - `EndpointListing.departed_endpoints` -> `departed_endpoint_count`,
101
+ `excluded_observer_endpoints` -> `excluded_observer_endpoint_count`, `total_discovered` ->
102
+ `total`.
103
+ - `HealthReport.dds_inactive_reason` -> `dds_inactive_note`, `payload_decoding_reason` ->
104
+ `payload_decoding_note`.
105
+
106
+ `list_endpoints`:
107
+
108
+ - `EndpointInfo.topic` -> `dds_topic` (raw name, `rt/scan`) plus `ros_topic` (`/scan`, `null`
109
+ with `ros_topic_note` for a DDS topic that is not a ROS 2 topic). `TopicSummary.topic` ->
110
+ `dds_topic` plus `ros_topic`.
111
+ - New input `include_internal` (default false): the ROS 2 service and action endpoints
112
+ (`rq/`, `rr/`, `rs/`, `rp/`, `ra/` and `ros_discovery_info`) are hidden unless it is true;
113
+ `hidden_internal_endpoint_count` says how many were left out.
114
+ - `EndpointInfo.activity` (always `null`) and `activity_note` (the same sentence on every
115
+ endpoint) are removed; the sentence is stated once in the new root-level `hints`.
116
+ - `EndpointInfo.domain_id` and `mode_effective` are removed (the listing carries them once).
117
+
118
+ Nested items no longer repeat the envelope: `mode_effective` is removed from
119
+ `ParticipantInfo`, `ParticipantEvent`, `EndpointInfo` and `MismatchReport`.
120
+
121
+ `stamp_source` (every `MessageSample`) is required and has five values: `header`, `payload`,
122
+ `recorded`, `dds_source`, `none`. It was nullable with four. `peek_dds_samples` now says
123
+ `dds_source` (builtin discovery topics, from the announcement timestamp) or `none`.
124
+
125
+ `sample_messages.timeout_s` accepts 1..40 (see Changed).
126
+
127
+ Configuration and Python API:
128
+
129
+ - `TOPICFORGE_DDS_BACKEND` accepts `mock`, `cyclone`, `fast` and `auto` only. `opendds` and
130
+ `dust` (stub adapters that never served) and the long-rejected `rti`, `opensplice`, `coredx`
131
+ and `intercom` now fail at startup with an error that names the valid values.
132
+ `health_check.dds_backend` no longer returns `opendds` or `dust`.
133
+ - `RosAdapter` (an alias of `MiddlewareAdapter`) is removed from `topicforge.adapters`.
134
+
135
+ ### Added (contract 2)
136
+
137
+ - `health_check`: `contract_version` (2), `rmw_implementation` and `rmw_source` (`env`,
138
+ `distro_default` or `none`; `ros2_cli` is reserved), and a `note`. The RMW comes from
139
+ `RMW_IMPLEMENTATION` or the default of `ROS_DISTRO`, never from the running graph.
140
+ - A `note` on `get_topic_info`, `detect_qos_mismatches`, `topic_metrics` and `health_check`.
141
+ - `analyze_bag` topics gain `kind`: `user`, `rosbag2_internal` (`/events/write_split`) or
142
+ `ros_builtin` (`/parameter_events`, `/rosout`).
143
+ - Reserved optional fields: `ParticipantInfo.node_names` and `node_names_source` (empty and
144
+ `none` until a tool fills them), `MismatchReport.suggested_fixes` (empty).
145
+ - `tests/test_contract_shape.py` (every tool returns one object and one text block),
146
+ `tests/test_field_names.py` (no `_seconds`, `_sec`, `_ms`, `_hertz`, `_reason` and similar
147
+ suffixes in any input or output field).
148
+
149
+ ### Added
150
+
151
+ - Ground-truth comparator in CI (`scripts/ground_truth/`): one `compare.py`, two producers of
152
+ ground truth. The OmniSim kit (external, manual, kept as confirmation) and the Docker bench
153
+ publisher, which now writes its own `ground_truth.json` in the same schema subset
154
+ (`tests/integration/ros2/ground_truth.py`: topics, types, endpoint counts and QoS, the
155
+ synthetic 541-beam scan and its sector minima, configured rates, `use_sim_time` per node,
156
+ RMW and distro). `drive.py` is the parameterised MCP stdio driver and `calls.json` the
157
+ default call list (graph calls before any sampling). Every `ros2-live.yml` matrix cell
158
+ (Humble/Jazzy x Fast DDS/Cyclone) runs both after the integration tests, fails on any FAIL
159
+ or SHAPE row, prints the measured rate `interval_cv` values (the `STABLE_CV` evidence for
160
+ `docs/CONTRACT.md` section 4) and uploads `COMPARISON.md`, `comparison.json` and the raw
161
+ results as an artifact. `compare.py` also reads a bag with `rosbags` (`--bag-truth`) to
162
+ check `analyze_bag` and `peek_bag_samples` independently.
163
+ - Two tools, additive: `list_nodes` and `get_node_info` (the surface is now fourteen tools).
164
+ - `list_nodes` -> `NodeListing {nodes, returned, total, truncated, duplicates,
165
+ mode_effective, note}`: one `ros2 node list` call, names, namespaces and the full names used
166
+ by more than one node (`duplicate_count`).
167
+ - `get_node_info(node, timeout_s)` -> `NodeInfo`: publishers, subscribers, service servers and
168
+ clients, action servers and clients (each `{name, type}`) from `ros2 node info`, then the
169
+ parameters from `ros2 param dump` (YAML on stdout, never an output option, so nothing is
170
+ written to disk) and `use_sim_time`. `timeout_s` is 1..20, default 8. A node that does not
171
+ answer its parameter read in time gives `parameters` null and a `parameters_note` (its
172
+ executor is probably blocked): a finding, not an error. A value whose name contains
173
+ password, secret, token, api_key or credential is masked; a value over 2048 characters or
174
+ 128 list elements (a robot description) is cut and flagged; the dump is capped at 1 MiB.
175
+ A name used by several nodes is flagged in `duplicate_count` and `note`. An unknown node
176
+ raises an error that lists close matches.
177
+ - Both run the `ros2` CLI in the ROS lane; DDS-only setups raise a clear error. The only
178
+ request TopicForge ever sends is a parameter read (list/get) (SECURITY.md).
179
+ - The `inspect-ros2-robot` prompt and plugin skill gain the node step.
180
+ - Per-type message summaries and an observed rate with a verdict on `sample_messages` and
181
+ `peek_bag_samples` (additive; no new input, no new tool).
182
+ - `MessageSample.summary`: `laser_scan` (beam count, angle geometry, finite / `inf` /
183
+ `-inf` / `nan` counts, closest obstacle, front / left / right / rear sector minima by
184
+ bearing in the sensor frame), `odometry` (speed, angular z, position, yaw), `imu` (roll,
185
+ pitch, yaw, angular velocity and acceleration norms, orientation validity), `image`
186
+ (size, encoding, step, buffer length) and `point_cloud2` (point count, layout). `null`
187
+ for any other type. Units are meters, radians and seconds with no unit suffix
188
+ (docs/CONTRACT.md section 4).
189
+ - The summary is computed on the whole message even when `payload` is cut: a scan is
190
+ streamed uncut (`--full-length`, up to 4 MiB per message while streaming), summarized,
191
+ then cut to `max_array_length` as before; `Image` and `PointCloud2` never read their
192
+ buffers; a bag scan is decoded whole.
193
+ - `SampleResult.rate`: `message_count`, `window_s`, mean / median / max interval,
194
+ `interval_cv`, `observed_frequency_hz` (wall clock: `received_ns`, or `recorded_ns` for a
195
+ bag), `sim_frequency_hz` (from the messages' own stamps), `trailing_gap_s` and a
196
+ `verdict`: `silent`, `insufficient`, `intermittent`, `stable`, `jittery` or `erratic`.
197
+ Thresholds confirmed at 0.2 and 0.5 on both bases; the `jittery` note gives the longest
198
+ gap as a multiple of the median. Physical quantities carry no unit suffix (CONTRACT 1.4).
199
+ - `peek_bag_samples` now returns non-finite floats as the strings `nan`, `inf`, `-inf`
200
+ (they could not be written as JSON numbers).
201
+ - Mock mode: `/scan` is a 720-beam scan in a walled room, and `/imu/data` is a new mock
202
+ topic.
203
+ - MCP prompts `diagnose-dds-bus` (optional `topic`, `symptom`) and `inspect-ros2-robot`
204
+ (optional `topic`, `bag_path`), the plugin skills in client-neutral wording, and server
205
+ `instructions` in the `initialize` result (read-only guarantee, which tool first,
206
+ `contract_version`) for clients that do not show prompts. They are snapshotted in
207
+ `tests/contract/_prompts.json`.
208
+ - `tests/test_tool_descriptions.py`: no history words (`v0.x`, `Phase`, `ceiling`, `TODO`,
209
+ `since 0.`) in any tool or field description, and every tool that takes a lane lock states
210
+ its 45 s bound.
211
+ - `docs/MIGRATION-0.7.md`: every old field and its new name, with before and after JSON.
212
+ - Optional local HTTP transport: `topicforge --transport streamable-http --port N` serves the
213
+ tools on `http://127.0.0.1:N/mcp`. stdio stays the default. The bind address is fixed to
214
+ 127.0.0.1 (no flag changes it) and DNS-rebinding protection is on (loopback `Host` and
215
+ `Origin` only). There is no authentication: use an SSH tunnel to reach a robot. See
216
+ `docs/CLIENTS.md`.
217
+ - `docs/CONTRACT.md`: the output contract (one object per tool, a single `note`, no silent
218
+ null, suffix vocabulary, enum casing by layer, non-finite floats, reserved optional fields,
219
+ rate verdicts) and the complete list of renames and splits.
220
+ - `docs/TOOLS.md`: tool reference generated from the served tools.
221
+ - Golden snapshots of every tool (`tests/contract/*.json`), generated by
222
+ `scripts/contract/snapshot_tools.py`, and `tests/test_contract_snapshots.py`, which fails
223
+ with a diff on any change to a description, schema or annotation and when `TOOLS.md` is
224
+ stale.
225
+
226
+ ### Removed
227
+
228
+ - The `opendds` and `dust` stub adapters (`topicforge.adapters.dds_opendds`,
229
+ `dds_dust`), their tests and the `requires_opendds` / `requires_dust` markers. The Pro tier
230
+ and `topicforge_pro` hook were already gone in 0.5.3: no code, setting or documentation
231
+ mentions `TOPICFORGE_LICENSE_KEY` any more.
232
+ - The `RosAdapter` alias (see Breaking).
233
+
234
+ ### Changed
235
+
236
+ - Tool descriptions state present behaviour only, name the lock they take and their worst-case
237
+ duration (45 s, lock wait included), and the description of `SampleResult.count` no longer
238
+ contradicts itself on the maximum of 50.
239
+ - Moved to the MCP Python SDK 2.x: the dependency is now `mcp>=2.3,<3` (it was `mcp>=1.0.0,<2`).
240
+ `FastMCP` became `MCPServer`; the server reports `name` and `version` through the
241
+ constructor, which replaces the private-attribute workaround of 0.6.4. Handlers, tool
242
+ names, parameters and the stdio transport are unchanged. The SDK brings new dependencies
243
+ (`mcp-types`, `httpx2`, `opentelemetry-api`, `anyio`, `pydantic>=2.12`). The served schemas
244
+ differ only where the SDK serializes differently (see the golden snapshot commit).
245
+ - Tool calls are serialized per backend (`topicforge.tools.guard`): the 2.x SDK runs
246
+ synchronous handlers in worker threads, and neither the Cyclone binding nor the `ros2` CLI
247
+ adapter can take overlapping calls. There are two locks: a ROS lane (six tools: `list_topics`,
248
+ `get_topic_info`, `list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`) and a
249
+ DDS lane (six tools: `list_participants`, `detect_qos_mismatches`, `peek_dds_samples`,
250
+ `participant_events`, `topic_metrics`, `list_endpoints`), so a slow `sample_messages` no longer delays a DDS call.
251
+ `health_check` and `peek_bag_samples` take no lock.
252
+ - A tool call has a wall budget of 45 s, lock wait included. A call that waited for its lane
253
+ runs with the time that is left (the `ros2` timeouts shrink accordingly), and one that cannot
254
+ start with at least 5 s left fails at once with `busy: another ros call is running, retry`
255
+ (or `dds`) instead of waiting. At most 16 calls queue per lane.
256
+ - `sample_messages` `timeout_s` now accepts 1..40 (it was 1..45), so that the timeout plus
257
+ stopping the CLI and decoding stays under the 45 s budget. The default is unchanged (10).
258
+ - `health_check` `sim_clock_published` no longer runs `ros2 topic info /clock`: it reports
259
+ what the last `list_topics` (or `get_topic_info` on `/clock`) saw, `null` before the first
260
+ graph read and two minutes after it. This is what lets `health_check` always answer.
261
+ - CI builds the wheel and installs it into a fresh venv on Ubuntu and Windows.
262
+
263
+ ### Fixed
264
+
265
+ - `rosbags` 0.11.6 is excluded: it builds message definitions with `PosixPath` and fails
266
+ on Windows for types with nested arrays, which broke opening some bags there.
267
+ - The server can no longer freeze on a hung `ros2` CLI. Every `ros2` call now goes through one
268
+ process runner (`adapters/ros2_live/process_runner.py`) instead of `subprocess.run`: the
269
+ process starts in its own kill unit (Windows Job Object with a `taskkill /T` fallback, POSIX
270
+ process group with SIGINT then SIGKILL), runs under a hard deadline, and its whole tree is
271
+ killed at the deadline, so the stock `ros2` launcher can no longer leave an orphan holding
272
+ the output pipes. Output is capped at 8 MiB per stream. Error messages are unchanged.
273
+ - `health_check` always answers, also while a `sample_messages` call is running.
274
+ - The server no longer blocks the asyncio event loop during a long tool call (a 45 s
275
+ `sample_messages`, a hung `ros2` CLI): handlers run in worker threads, so the transport
276
+ keeps answering pings and cancellations.
277
+ - `AdapterError` messages still reach the client as `isError` text. The 2.x SDK keeps the
278
+ text of any exception other than `ToolError` on the server, so every handler now
279
+ re-raises `AdapterError` as `ToolError` with the same message. Unexpected exceptions
280
+ stay redacted ("Error executing tool X") and leak no traceback.
281
+ - `rate.observed_frequency_hz` on `received_ns` no longer counts the start-up drain of `ros2 topic echo` (up to 5 queued messages printed at once, which read `/clock` at 91 Hz for 50 Hz): the leading run of near-zero intervals is set aside and reported in the new additive `startup_burst_count`; the ground-truth comparator gates FAIL and STABLE_CV on 10 messages spanning 0.9 s.
282
+
10
283
  ## [0.6.4] - 2026-10-08
11
284
 
12
285
  ### Added
@@ -1388,7 +1661,9 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
1388
1661
  - The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
1389
1662
  - `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
1390
1663
 
1391
- [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.6.4...HEAD
1664
+ [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.7.1...HEAD
1665
+ [0.7.1]: https://github.com/yaniswav/TopicForge/compare/v0.7.0...v0.7.1
1666
+ [0.7.0]: https://github.com/yaniswav/TopicForge/compare/v0.6.4...v0.7.0
1392
1667
  [0.6.4]: https://github.com/yaniswav/TopicForge/compare/v0.6.3...v0.6.4
1393
1668
  [0.6.3]: https://github.com/yaniswav/TopicForge/compare/v0.6.2...v0.6.3
1394
1669
  [0.6.2]: https://github.com/yaniswav/TopicForge/compare/v0.6.1...v0.6.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: topicforge
3
- Version: 0.6.4
3
+ Version: 0.7.1
4
4
  Summary: ROS Topic Inspector & Bag Analyzer MCP server for AI agents
5
5
  Project-URL: Homepage, https://github.com/yaniswav/TopicForge
6
6
  Project-URL: Repository, https://github.com/yaniswav/TopicForge
@@ -39,12 +39,13 @@ Classifier: Programming Language :: Python :: 3.10
39
39
  Classifier: Programming Language :: Python :: 3.11
40
40
  Classifier: Programming Language :: Python :: 3.12
41
41
  Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Programming Language :: Python :: 3.14
42
43
  Requires-Python: >=3.10
43
44
  Requires-Dist: cyclonedds>=0.10; python_version < '3.14' and ((platform_system == 'Linux' and platform_machine == 'x86_64') or (platform_system == 'Windows' and platform_machine == 'AMD64') or platform_system == 'Darwin')
44
- Requires-Dist: mcp<2,>=1.0.0
45
+ Requires-Dist: mcp<3,>=2.3
45
46
  Requires-Dist: pydantic>=2.6
46
47
  Requires-Dist: pyyaml>=6
47
- Requires-Dist: rosbags>=0.11.3
48
+ Requires-Dist: rosbags!=0.11.6,>=0.11.3
48
49
  Provides-Extra: all
49
50
  Requires-Dist: cyclonedds>=0.10; extra == 'all'
50
51
  Provides-Extra: bags
@@ -72,9 +73,9 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
72
73
 
73
74
  Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
74
75
 
75
- It gives the agent twelve typed tools that return frozen Pydantic schemas, identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
76
+ It gives the agent fourteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
76
77
 
77
- For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. So far the author has observed Cyclone DDS and Dust DDS participants on a live bus. RTI Connext, OpenDDS, CoreDX and Fast DDS announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
78
+ For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
78
79
 
79
80
  ## Quickstart
80
81
 
@@ -100,21 +101,23 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
100
101
  }
101
102
  ```
102
103
 
103
- Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
104
+ Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
104
105
 
105
- **Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjYuNCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.6.4%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
106
+ **Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
106
107
 
107
108
  Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
108
109
 
109
110
  ## Tools
110
111
 
111
- Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
112
+ Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures, and a single `note` that explains anything unusual. `health_check.contract_version` is `2`. The full reference, with every field, is generated in [`docs/TOOLS.md`](docs/TOOLS.md); upgrading from 0.6.x: [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md).
112
113
 
113
114
  | Tool | Purpose |
114
115
  | ----------------------- | ------------------------------------------------------------------------------------------------ |
115
116
  | `health_check` | Environment and mode introspection. Always succeeds; reports `mode` next to `requested_mode` |
116
117
  | `list_topics` | Discover the ROS2 graph |
117
118
  | `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
119
+ | `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
120
+ | `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
118
121
  | `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
119
122
  | `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
120
123
  | `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
@@ -125,6 +128,8 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
125
128
  | `peek_bag_samples` | Decoded samples from a recorded bag, including ROS 1 `.bag` (`rosbags` ships with the package) |
126
129
  | `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
127
130
 
131
+ The server also offers two MCP prompts, `diagnose-dds-bus` and `inspect-ros2-robot`, that give a client the read-only call order, and short server `instructions` for clients that do not show prompts (see [`docs/CLIENTS.md`](docs/CLIENTS.md#prompts-and-instructions)).
132
+
128
133
  Walkthroughs against the mock, each with the exact tool calls and payloads, are in [`examples/`](examples/README.md). To run the DDS tools against a real bus with several programs and vendors, see [`examples/dds/README.md`](examples/dds/README.md) (`python examples/dds/run_all.py`).
129
134
 
130
135
  ## Modes
@@ -145,9 +150,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
145
150
  TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
146
151
  ```
147
152
 
148
- `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_reason`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the five ROS2 graph and bag tools to the CLI and the seven DDS tools to the DDS backend.
153
+ `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the six `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
149
154
 
150
- A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. `opendds` and `dust` are permanent stubs that never serve. `rti`, `opensplice`, `coredx` and `intercom` are rejected with a configuration error,; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
155
+ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
151
156
 
152
157
  ## Configuration reference
153
158
 
@@ -157,26 +162,28 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
157
162
  | `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
158
163
  | `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
159
164
  | `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
160
- | `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto` (`opendds` and `dust` are stubs) |
165
+ | `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto`; anything else is rejected |
161
166
  | `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
162
167
  | `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
163
168
 
169
+ Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`) and `--port N` (default 8765, only with `streamable-http`). The HTTP transport binds `127.0.0.1` only, with DNS-rebinding protection and no authentication; see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
170
+
164
171
  Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
165
172
 
166
173
  ## Limitations
167
174
 
168
- External validation against a simulated robot's ground truth: [docs/VALIDATION.md](docs/VALIDATION.md).
175
+ External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
169
176
 
170
- - DDS validation is partial. The Cyclone adapter has run against a real bus, with Cyclone and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`). The Fast DDS adapter has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
177
+ - DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). The Fast DDS adapter (TopicForge joining through the `fastdds` binding) has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
171
178
  - User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
172
179
  - Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
173
180
  - Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
174
181
  - Single domain: the server observes the domain it joined at startup; changing it needs a restart.
175
182
  - DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
176
183
  - Fast DDS serves no `list_endpoints`.
177
- - `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..45, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
184
+ - `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
178
185
  - `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
179
- - Synchronous handlers: the tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
186
+ - Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
180
187
  - No streaming or push subscriptions: tools are strictly request/response.
181
188
 
182
189
  Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
@@ -195,10 +202,10 @@ When on, each tool call emits one event with exactly six fields:
195
202
 
196
203
  | Field | Example | Notes |
197
204
  | ------------ | --------------- | ----------------------------------------------------------- |
198
- | `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
205
+ | `tool_name` | `"list_topics"` | One of the fourteen tools, never argument values |
199
206
  | `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
200
207
  | `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
201
- | `version` | `"0.6.4"` | TopicForge server version |
208
+ | `version` | `"0.7.1"` | TopicForge server version |
202
209
  | `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
203
210
  | `success` | `true` | Whether the handler returned or raised |
204
211
 
@@ -230,14 +237,14 @@ Tests run against the mock adapter, the live adapter's pure parsers and the bind
230
237
 
231
238
  ## Upgrading
232
239
 
233
- TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras.
240
+ TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras. 0.7.0 changed the shape of tool results (`contract_version` 2): [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md) maps every old field to its new one.
234
241
 
235
242
  ## Layout
236
243
 
237
244
  ```
238
245
  src/topicforge/
239
- server/ MCP bootstrap, build_app(settings)
240
- tools/ thin FastMCP handlers, no backend logic
246
+ server/ MCP bootstrap, build_app(settings), prompts and instructions
247
+ tools/ thin MCPServer handlers, no backend logic
241
248
  services/ input validation, orchestration, adapter factory
242
249
  adapters/ ros2_live, ros2_mock, dds_cyclone, dds_fast, common/ (binding-free logic)
243
250
  models/ frozen Pydantic schemas, the contract with MCP clients
@@ -12,9 +12,9 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
12
12
 
13
13
  Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
14
14
 
15
- It gives the agent twelve typed tools that return frozen Pydantic schemas, identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
15
+ It gives the agent fourteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
16
16
 
17
- For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. So far the author has observed Cyclone DDS and Dust DDS participants on a live bus. RTI Connext, OpenDDS, CoreDX and Fast DDS announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
17
+ For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
18
18
 
19
19
  ## Quickstart
20
20
 
@@ -40,21 +40,23 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
40
40
  }
41
41
  ```
42
42
 
43
- Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
43
+ Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
44
44
 
45
- **Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjYuNCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.6.4%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
45
+ **Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
46
46
 
47
47
  Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
48
48
 
49
49
  ## Tools
50
50
 
51
- Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
51
+ Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures, and a single `note` that explains anything unusual. `health_check.contract_version` is `2`. The full reference, with every field, is generated in [`docs/TOOLS.md`](docs/TOOLS.md); upgrading from 0.6.x: [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md).
52
52
 
53
53
  | Tool | Purpose |
54
54
  | ----------------------- | ------------------------------------------------------------------------------------------------ |
55
55
  | `health_check` | Environment and mode introspection. Always succeeds; reports `mode` next to `requested_mode` |
56
56
  | `list_topics` | Discover the ROS2 graph |
57
57
  | `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
58
+ | `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
59
+ | `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
58
60
  | `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
59
61
  | `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
60
62
  | `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
@@ -65,6 +67,8 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
65
67
  | `peek_bag_samples` | Decoded samples from a recorded bag, including ROS 1 `.bag` (`rosbags` ships with the package) |
66
68
  | `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
67
69
 
70
+ The server also offers two MCP prompts, `diagnose-dds-bus` and `inspect-ros2-robot`, that give a client the read-only call order, and short server `instructions` for clients that do not show prompts (see [`docs/CLIENTS.md`](docs/CLIENTS.md#prompts-and-instructions)).
71
+
68
72
  Walkthroughs against the mock, each with the exact tool calls and payloads, are in [`examples/`](examples/README.md). To run the DDS tools against a real bus with several programs and vendors, see [`examples/dds/README.md`](examples/dds/README.md) (`python examples/dds/run_all.py`).
69
73
 
70
74
  ## Modes
@@ -85,9 +89,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
85
89
  TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
86
90
  ```
87
91
 
88
- `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_reason`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the five ROS2 graph and bag tools to the CLI and the seven DDS tools to the DDS backend.
92
+ `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the six `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
89
93
 
90
- A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. `opendds` and `dust` are permanent stubs that never serve. `rti`, `opensplice`, `coredx` and `intercom` are rejected with a configuration error,; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
94
+ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
91
95
 
92
96
  ## Configuration reference
93
97
 
@@ -97,26 +101,28 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
97
101
  | `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
98
102
  | `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
99
103
  | `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
100
- | `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto` (`opendds` and `dust` are stubs) |
104
+ | `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto`; anything else is rejected |
101
105
  | `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
102
106
  | `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
103
107
 
108
+ Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`) and `--port N` (default 8765, only with `streamable-http`). The HTTP transport binds `127.0.0.1` only, with DNS-rebinding protection and no authentication; see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
109
+
104
110
  Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
105
111
 
106
112
  ## Limitations
107
113
 
108
- External validation against a simulated robot's ground truth: [docs/VALIDATION.md](docs/VALIDATION.md).
114
+ External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
109
115
 
110
- - DDS validation is partial. The Cyclone adapter has run against a real bus, with Cyclone and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`). The Fast DDS adapter has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
116
+ - DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). The Fast DDS adapter (TopicForge joining through the `fastdds` binding) has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
111
117
  - User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
112
118
  - Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
113
119
  - Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
114
120
  - Single domain: the server observes the domain it joined at startup; changing it needs a restart.
115
121
  - DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
116
122
  - Fast DDS serves no `list_endpoints`.
117
- - `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..45, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
123
+ - `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
118
124
  - `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
119
- - Synchronous handlers: the tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
125
+ - Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
120
126
  - No streaming or push subscriptions: tools are strictly request/response.
121
127
 
122
128
  Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
@@ -135,10 +141,10 @@ When on, each tool call emits one event with exactly six fields:
135
141
 
136
142
  | Field | Example | Notes |
137
143
  | ------------ | --------------- | ----------------------------------------------------------- |
138
- | `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
144
+ | `tool_name` | `"list_topics"` | One of the fourteen tools, never argument values |
139
145
  | `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
140
146
  | `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
141
- | `version` | `"0.6.4"` | TopicForge server version |
147
+ | `version` | `"0.7.1"` | TopicForge server version |
142
148
  | `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
143
149
  | `success` | `true` | Whether the handler returned or raised |
144
150
 
@@ -170,14 +176,14 @@ Tests run against the mock adapter, the live adapter's pure parsers and the bind
170
176
 
171
177
  ## Upgrading
172
178
 
173
- TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras.
179
+ TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras. 0.7.0 changed the shape of tool results (`contract_version` 2): [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md) maps every old field to its new one.
174
180
 
175
181
  ## Layout
176
182
 
177
183
  ```
178
184
  src/topicforge/
179
- server/ MCP bootstrap, build_app(settings)
180
- tools/ thin FastMCP handlers, no backend logic
185
+ server/ MCP bootstrap, build_app(settings), prompts and instructions
186
+ tools/ thin MCPServer handlers, no backend logic
181
187
  services/ input validation, orchestration, adapter factory
182
188
  adapters/ ros2_live, ros2_mock, dds_cyclone, dds_fast, common/ (binding-free logic)
183
189
  models/ frozen Pydantic schemas, the contract with MCP clients