topicforge 0.5.4__tar.gz → 0.5.6__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 (171) hide show
  1. {topicforge-0.5.4 → topicforge-0.5.6}/.gitignore +7 -36
  2. {topicforge-0.5.4 → topicforge-0.5.6}/CHANGELOG.md +186 -96
  3. {topicforge-0.5.4 → topicforge-0.5.6}/PKG-INFO +36 -31
  4. {topicforge-0.5.4 → topicforge-0.5.6}/README.md +33 -28
  5. topicforge-0.5.6/docs/DDS_QUICKSTART.md +126 -0
  6. {topicforge-0.5.4 → topicforge-0.5.6}/docs/TESTING.md +7 -7
  7. {topicforge-0.5.4 → topicforge-0.5.6}/docs/TROUBLESHOOTING.md +11 -11
  8. {topicforge-0.5.4 → topicforge-0.5.6}/docs/TUTORIEL.md +17 -21
  9. topicforge-0.5.6/docs/dds-interop-matrix.md +55 -0
  10. {topicforge-0.5.4 → topicforge-0.5.6}/examples/README.md +3 -3
  11. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/00_hello_pub_sub/README.md +17 -23
  12. topicforge-0.5.6/examples/dds/01_who_is_on_the_bus/README.md +47 -0
  13. topicforge-0.5.6/examples/dds/02_why_cant_they_talk/README.md +60 -0
  14. topicforge-0.5.6/examples/dds/03_a_node_crashed/README.md +56 -0
  15. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/04_late_joiner_misses_data/README.md +14 -17
  16. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/05_reliability_in_code/README.md +14 -16
  17. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/06_durability_late_joiner_in_code/README.md +24 -30
  18. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/07_deadline_in_code/README.md +15 -22
  19. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/08_crash_seen_from_inside/README.md +24 -31
  20. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/10_lidar_silent_after_driver_swap/README.md +20 -19
  21. topicforge-0.5.6/examples/dds/11_who_talks_to_whom/README.md +50 -0
  22. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/12_safety_monitor_dropout/README.md +17 -25
  23. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/13_deadline_not_offered/README.md +18 -23
  24. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/14_restart_loop/README.md +16 -19
  25. {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/README.md +13 -9
  26. {topicforge-0.5.4 → topicforge-0.5.6}/pyproject.toml +4 -4
  27. topicforge-0.5.6/scripts/agent_eval/README.md +38 -0
  28. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/README.md +12 -14
  29. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/fast_publisher_cpp/README.md +2 -2
  30. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/fast_py/README.md +3 -3
  31. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/opensplice_publisher/README.md +2 -2
  32. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/__init__.py +1 -1
  33. topicforge-0.5.6/src/topicforge/adapters/base.py +131 -0
  34. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/__init__.py +67 -1
  35. topicforge-0.5.6/src/topicforge/adapters/common/cdr_decoder.py +176 -0
  36. topicforge-0.5.6/src/topicforge/adapters/common/dds_helpers.py +237 -0
  37. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/dds_introspection.py +18 -36
  38. topicforge-0.5.6/src/topicforge/adapters/common/discovery_tracker.py +491 -0
  39. topicforge-0.5.6/src/topicforge/adapters/common/endpoints.py +395 -0
  40. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/lifecycle.py +68 -76
  41. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/metrics_buffer.py +36 -92
  42. topicforge-0.5.6/src/topicforge/adapters/common/qos_analyzer.py +313 -0
  43. topicforge-0.5.6/src/topicforge/adapters/common/qos_endpoints.py +75 -0
  44. topicforge-0.5.6/src/topicforge/adapters/common/qos_normalize.py +308 -0
  45. topicforge-0.5.6/src/topicforge/adapters/common/qos_scan.py +423 -0
  46. topicforge-0.5.6/src/topicforge/adapters/common/topic_filter.py +106 -0
  47. topicforge-0.5.6/src/topicforge/adapters/common/xtypes.py +97 -0
  48. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/composite.py +50 -27
  49. topicforge-0.5.6/src/topicforge/adapters/dds_cyclone/adapter.py +559 -0
  50. topicforge-0.5.6/src/topicforge/adapters/dds_dust/__init__.py +10 -0
  51. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_dust/adapter.py +33 -24
  52. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_fast/adapter.py +85 -165
  53. topicforge-0.5.6/src/topicforge/adapters/dds_opendds/__init__.py +11 -0
  54. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_opendds/adapter.py +37 -34
  55. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_live/adapter.py +205 -138
  56. topicforge-0.5.6/src/topicforge/adapters/ros2_live/parsers.py +113 -0
  57. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/adapter.py +30 -23
  58. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/fixtures.py +158 -104
  59. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/config/settings.py +55 -67
  60. topicforge-0.5.6/src/topicforge/constants.py +25 -0
  61. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/models/__init__.py +16 -0
  62. topicforge-0.5.6/src/topicforge/models/schemas.py +1355 -0
  63. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/server/app.py +6 -16
  64. topicforge-0.5.6/src/topicforge/services/bag_service.py +412 -0
  65. topicforge-0.5.6/src/topicforge/services/bag_stats.py +218 -0
  66. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/services/factory.py +83 -55
  67. topicforge-0.5.6/src/topicforge/services/health.py +112 -0
  68. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/services/inspector.py +110 -51
  69. topicforge-0.5.6/src/topicforge/services/sample_budget.py +53 -0
  70. topicforge-0.5.6/src/topicforge/telemetry/__init__.py +22 -0
  71. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/telemetry/client.py +19 -41
  72. topicforge-0.5.6/src/topicforge/tools/handlers.py +639 -0
  73. {topicforge-0.5.4 → topicforge-0.5.6}/tests/conftest.py +16 -0
  74. topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +20 -0
  75. topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +104 -0
  76. topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_scan.txt +20 -0
  77. topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_tf.txt +34 -0
  78. topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +34 -0
  79. topicforge-0.5.6/tests/integration/ros2/Dockerfile +28 -0
  80. topicforge-0.5.6/tests/integration/ros2/__init__.py +0 -0
  81. topicforge-0.5.6/tests/integration/ros2/entrypoint.sh +43 -0
  82. topicforge-0.5.6/tests/integration/ros2/publisher.py +112 -0
  83. topicforge-0.5.6/tests/integration/ros2/run_bench.py +79 -0
  84. topicforge-0.5.6/tests/integration/ros2/test_live_adapter.py +191 -0
  85. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_analyze_bag_multi_format.py +5 -10
  86. topicforge-0.5.6/tests/test_bag_omnisim_humble.py +150 -0
  87. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_bag_service.py +1 -3
  88. topicforge-0.5.6/tests/test_bag_stats.py +291 -0
  89. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_cdr_decoder.py +34 -13
  90. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_composite_adapter.py +13 -10
  91. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_config.py +7 -7
  92. topicforge-0.5.6/tests/test_cyclone_adapter.py +226 -0
  93. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_cross_vendor.py +6 -11
  94. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_helpers.py +36 -9
  95. topicforge-0.5.6/tests/test_dds_inactive_reason.py +94 -0
  96. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_introspection.py +4 -5
  97. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_qos_normalization.py +48 -6
  98. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_schemas.py +1 -1
  99. topicforge-0.5.6/tests/test_discovery_tracker.py +462 -0
  100. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dust_adapter.py +3 -4
  101. topicforge-0.5.6/tests/test_endpoints.py +444 -0
  102. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_example_node_spec.py +72 -0
  103. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_factory.py +5 -4
  104. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_fast_adapter.py +3 -3
  105. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_health.py +22 -0
  106. topicforge-0.5.6/tests/test_history_and_hints.py +273 -0
  107. topicforge-0.5.6/tests/test_honest_outputs.py +250 -0
  108. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_inspector.py +1 -1
  109. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_lifecycle_buffer.py +2 -2
  110. topicforge-0.5.6/tests/test_live_adapter_graph.py +404 -0
  111. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_metrics_buffer.py +7 -9
  112. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_mock_adapter.py +23 -7
  113. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_opendds_adapter.py +9 -9
  114. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_peek_bag_samples.py +1 -1
  115. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_qos_analyzer.py +208 -6
  116. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_qos_endpoints.py +18 -13
  117. topicforge-0.5.6/tests/test_qos_scan.py +386 -0
  118. topicforge-0.5.6/tests/test_sample_options.py +217 -0
  119. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_telemetry.py +3 -3
  120. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_tools_integration.py +15 -11
  121. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_topic_metrics.py +1 -1
  122. topicforge-0.5.4/docs/DDS_QUICKSTART.md +0 -121
  123. topicforge-0.5.4/docs/dds-interop-matrix.md +0 -58
  124. topicforge-0.5.4/docs/pro.md +0 -38
  125. topicforge-0.5.4/docs/product-plan.md +0 -183
  126. topicforge-0.5.4/examples/dds/01_who_is_on_the_bus/README.md +0 -58
  127. topicforge-0.5.4/examples/dds/02_why_cant_they_talk/README.md +0 -61
  128. topicforge-0.5.4/examples/dds/03_a_node_crashed/README.md +0 -61
  129. topicforge-0.5.4/examples/dds/11_who_talks_to_whom/README.md +0 -53
  130. topicforge-0.5.4/src/topicforge/adapters/base.py +0 -134
  131. topicforge-0.5.4/src/topicforge/adapters/common/cdr_decoder.py +0 -203
  132. topicforge-0.5.4/src/topicforge/adapters/common/dds_helpers.py +0 -202
  133. topicforge-0.5.4/src/topicforge/adapters/common/qos_analyzer.py +0 -98
  134. topicforge-0.5.4/src/topicforge/adapters/common/qos_endpoints.py +0 -95
  135. topicforge-0.5.4/src/topicforge/adapters/common/qos_normalize.py +0 -182
  136. topicforge-0.5.4/src/topicforge/adapters/common/xtypes.py +0 -120
  137. topicforge-0.5.4/src/topicforge/adapters/dds_cyclone/adapter.py +0 -651
  138. topicforge-0.5.4/src/topicforge/adapters/dds_dust/__init__.py +0 -12
  139. topicforge-0.5.4/src/topicforge/adapters/dds_opendds/__init__.py +0 -15
  140. topicforge-0.5.4/src/topicforge/constants.py +0 -28
  141. topicforge-0.5.4/src/topicforge/models/schemas.py +0 -706
  142. topicforge-0.5.4/src/topicforge/services/bag_service.py +0 -273
  143. topicforge-0.5.4/src/topicforge/services/health.py +0 -82
  144. topicforge-0.5.4/src/topicforge/telemetry/__init__.py +0 -27
  145. topicforge-0.5.4/src/topicforge/tools/handlers.py +0 -453
  146. topicforge-0.5.4/tests/test_cyclone_adapter.py +0 -120
  147. {topicforge-0.5.4 → topicforge-0.5.6}/LICENSE +0 -0
  148. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_c/README.md +0 -0
  149. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
  150. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
  151. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/dust_py/README.md +0 -0
  152. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/rti_c/README.md +0 -0
  153. {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/rti_cpp/README.md +0 -0
  154. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/__main__.py +0 -0
  155. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/__init__.py +0 -0
  156. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
  157. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
  158. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
  159. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
  160. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/config/__init__.py +0 -0
  161. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/server/__init__.py +0 -0
  162. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/services/__init__.py +0 -0
  163. {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/tools/__init__.py +0 -0
  164. {topicforge-0.5.4 → topicforge-0.5.6}/tests/__init__.py +0 -0
  165. {topicforge-0.5.4 → topicforge-0.5.6}/tests/fixtures/csv_echo_imu.txt +0 -0
  166. {topicforge-0.5.4 → topicforge-0.5.6}/tests/fixtures/csv_echo_pose_multi.txt +0 -0
  167. {topicforge-0.5.4 → topicforge-0.5.6}/tests/integration/__init__.py +0 -0
  168. {topicforge-0.5.4 → topicforge-0.5.6}/tests/integration/test_real_bus.py +0 -0
  169. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_live_adapter_parse.py +0 -0
  170. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_live_adapter_subprocess.py +0 -0
  171. {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_xtypes.py +0 -0
@@ -160,6 +160,8 @@ dump.rdb
160
160
  *.db3
161
161
  *.mcap
162
162
  rosbag2_*/
163
+ # Test fixtures that are meant to be committed
164
+ !tests/fixtures/bags/**/*.db3
163
165
 
164
166
 
165
167
  # -------------------------------------------------------------------------
@@ -187,42 +189,8 @@ personal/
187
189
  CLAUDE*.md
188
190
 
189
191
 
190
- # -------------------------------------------------------------------------
191
- # Project-local docs and drafts kept out of the package / out of clients.
192
- # Allowlist exceptions: the folder README explains the convention, and
193
- # `*-spec.md` files are committable specs produced by pack-growth streams
194
- # (e.g. Stream C of a release plan). Everything else under projet-file/
195
- # stays local: PDFs, personal market briefs, raw strategy notes.
196
- # -------------------------------------------------------------------------
197
- /docs/projet-file/**
198
- !/docs/projet-file/README.md
199
- !/docs/projet-file/*-spec.md
200
- # Traction snapshots are versioned: weekly JSON + summary + folder README.
201
- # This is the historical curve that informs decision gates G1/G2/G3
202
- # (product-plan section 12). Numeric history must survive across machines.
203
- !/docs/projet-file/traction/
204
- !/docs/projet-file/traction/**
205
- # Launch-post drafts (Reddit, LinkedIn, X). Versioned so the maintainer
206
- # can diff drafts across releases and keep marketing history auditable.
207
- # Drafts, not production copy, never the public README.
208
- !/docs/projet-file/launch-posts/
209
- !/docs/projet-file/launch-posts/**
210
- # Audit reports (security, architecture) + audit-followup triage docs.
211
- # Versioned so audit trails survive across machines and the triage
212
- # decisions are bisectable per release.
213
- !/docs/projet-file/*-audit-*.md
214
- !/docs/projet-file/audit-*.md
215
- # External reference material (OMG interop reports, third-party specs
216
- # snapshots) the maintainer wants pinned in git so strategy decisions
217
- # remain reproducible. Tracked, not part of sdist.
218
- !/docs/projet-file/references/
219
- !/docs/projet-file/references/**
220
- # Archive of superseded strategic artifacts (old audit reports, dated
221
- # launch-post drafts). Kept in git so the historical context survives
222
- # but tucked away so the active `projet-file/` folder stays focused on
223
- # what is currently load-bearing.
224
- !/docs/projet-file/archive/
225
- !/docs/projet-file/archive/**
192
+ # Owner's private strategy notes, specs and traction snapshots: local only.
193
+ /docs/projet-file/
226
194
  /docs/assets/screencast-raw/
227
195
 
228
196
 
@@ -235,3 +203,6 @@ pro/
235
203
  scripts/integration/**/rti_license.dat
236
204
  scripts/integration/**/*.dat
237
205
  .venv-demo/
206
+
207
+ # Owner's traction snapshot script (runs from a local scheduled task): local only.
208
+ /scripts/traction-snapshot.sh
@@ -5,113 +5,201 @@ All notable changes to TopicForge are documented in this file.
5
5
  The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.5.6] - 2026-10-02
9
+
10
+ Fixes from a live run of 0.5.3 and 0.5.5 against OmniSim's simulated
11
+ Clearpath Husky (ROS 2 Humble, Fast DDS, Cyclone backend for the DDS tools),
12
+ reported with ground truth by the OmniSim team.
13
+
14
+ ### Added
15
+
16
+ - `sample_messages` takes `max_array_length` (1..65536, default 128, null for no
17
+ cut) and `arrays_summary_only`. A cut is listed under `_truncated_after_columns`
18
+ in the sample and in `note`.
19
+ - Size caps on returned samples: 1 MiB per message and 4 MiB per call
20
+ (`TOPICFORGE_MAX_SAMPLE_BYTES` sets the per-message cap). Over-cap messages are
21
+ dropped and `note` says so. `peek_bag_samples` is capped the same way, and its
22
+ arrays are cut at 4096 elements.
23
+ - `BagTopicStats` gains `first_timestamp_ns`, `last_timestamp_ns`,
24
+ `frequency_basis` (`topic_span` or `bag_duration`) and `latched`.
25
+ - `TopicInfo.qos_durability`; `qos_reliability` and `qos_durability` are filled
26
+ by `get_topic_info` from the publishers' QoS (`mixed` when they disagree).
27
+ - `HealthReport.dds_inactive_reason` says why `dds_backend` is `none`.
28
+ - A real Humble rosbag2 bag from the OmniSim team as a test fixture
29
+ (`tests/fixtures/bags/omnisim_humble/`).
30
+
31
+ ### Changed
32
+
33
+ - `list_topics` (live) uses one `ros2 topic list -v` call for publisher and
34
+ subscriber counts instead of one `ros2 topic info` per topic, falling back to
35
+ the per-topic calls if the output is not recognized. It leaves QoS null.
36
+ - `rosbags>=0.11.3` is required (0.10 reads a bare `.db3` as ROS 1 and returns
37
+ message definitions and QoS as plain strings).
38
+ - A latched topic gets no rate only when its messages span under 1 second (a
39
+ start-up burst such as `/tf_static`); a latched topic published over a longer
40
+ span keeps its rate. `latched` is unchanged.
41
+ - `analyze_bag` reads per-topic times of an `.mcap` bag only up to 200 MiB and
42
+ 5 s; past either it keeps `bag_duration` rates and says so in the new
43
+ `BagAnalysis.note`.
44
+ - The `policies_checked` entry for History reads "History (risky only, where
45
+ announced)".
46
+ - `max_array_length` also cuts strings and bytes to that many characters plus
47
+ `...`; those cells are listed under `_truncated_columns`.
48
+ - `QosProfile.history` is now optional, and a new `history_note` explains why it
49
+ is missing. DDS discovery does not carry History (the builtin endpoint data has
50
+ no such member), so TopicForge reports it only for its own endpoints and for
51
+ Cyclone DDS peers that set something other than the default. For Fast DDS, RTI
52
+ and unknown-vendor endpoints it is `null`; for a Cyclone peer, KEEP_LAST depth 1
53
+ is also `null` because the binding fills missing QoS with that default. Clients
54
+ that assumed `history` is always a string must handle `null`.
55
+ - A discovered endpoint that announced no History keeps its reliability,
56
+ durability and the other policies; before, the whole `qos` became `null`.
57
+ - `detect_qos_mismatches` judges the KEEP_ALL-vs-KEEP_LAST History risk only
58
+ where both sides announced History; elsewhere one hint states that discovery
59
+ does not carry it, instead of a warning on every pair.
60
+
61
+ ### Fixed
62
+
63
+ - `sample_messages` with `arrays_summary_only` shifted every CSV column after an
64
+ array: `<sequence type: float, length: 541>` contains a comma and was split in
65
+ two. It is one cell now.
66
+ - `sample_messages` returned no samples and no explanation when the echo timed
67
+ out (for example a large message with `max_array_length` null); `note` now says
68
+ so.
69
+ - `list_topics` reported 0 publishers and 0 subscribers for a topic missing from
70
+ `ros2 topic list -v`; it now asks `ros2 topic info` for that topic.
71
+ - `peek_bag_samples` converted a whole numpy array to a list before cutting it
72
+ at 4096 elements; it now converts only the part it keeps.
73
+ - Fast DDS endpoints no longer report a History taken from the binding's
74
+ defaults: `detect_qos_mismatches` treats it as not announced, like the other
75
+ vendors. This path has never run against a real Fast DDS bus.
76
+ - The `tests/fixtures/bags` bag is left out of the sdist.
77
+ - `peek_bag_samples` failed on every Humble `.db3` bag with "Bag contains no
78
+ type definitions". The reader now gets the type definitions of the distro the
79
+ bag records, or Humble, and `note` says which. `LaserScan.ranges` and other
80
+ numeric arrays now come back as lists, not a numpy repr string.
81
+ - `analyze_bag` rates were count / whole-bag duration, 0.1 to 0.7 percent off on
82
+ periodic topics and meaningless on latched ones (`/tf_static` showed 0.06 Hz,
83
+ `/rosout` 0.37 Hz). They are now `(n - 1) / (last - first)` per topic, read
84
+ from the bag when it is readable locally, and latched topics are flagged.
85
+ - `get_topic_info` returned `qos_reliability: null` on every topic although the
86
+ CLI prints Reliability and Durability per endpoint.
87
+ - `peek_dds_samples('/scan')` reported the topic as not discovered while
88
+ `rt/scan` and `scan` worked; it now resolves the name like the other DDS tools
89
+ and says which topic matched.
90
+ - The "DDS module is not active" error said to install the Cyclone binding even
91
+ when it was installed. It now states the actual cause: backend not selected,
92
+ binding missing, or adapter failed to start. README wording aligned.
93
+ - CSV `...` truncation cells from `ros2 topic echo` no longer count as data
94
+ columns.
95
+ - `ros2` output is decoded as UTF-8 on Windows instead of cp1252.
96
+ - `detect_qos_mismatches` no longer calls service, action, `rosout`,
97
+ `parameter_events` or `ros_discovery_info` topics typos of each other (for
98
+ example `get_parametersRequest` vs `set_parametersRequest`), and no longer
99
+ reports them as orphans. Only plain `rt/` topics and bare DDS names are compared.
100
+ - The "differs by N edits" hint is now only given between a writer-only name and
101
+ a reader-only name; a topic with both sides is never suggested as a typo.
102
+
103
+ ## [0.5.5] - 2026-10-02
104
+
105
+ Tool outputs were reworked after testing them with LLM agents on the author's
106
+ 16 test scenarios (live DDS buses with planted faults).
107
+
108
+ ### Breaking
109
+
110
+ - `detect_qos_mismatches` returns a `MismatchScan` envelope instead of a bare
111
+ list. Migrate by reading `["reports"]` where you used the list.
112
+
113
+ ### Added
114
+
115
+ - `list_endpoints`, the twelfth tool: every announced DDS writer and reader with
116
+ owning participant, structured QoS and a per-topic roll-up that flags orphans
117
+ (`no_reader`, `no_writer`). Cyclone and mock only.
118
+ - Continuous discovery tracking on Cyclone, so a node restarted three times
119
+ shows as 3 `lost` and 4 `discovered` events dated by DDS, not by poll time.
120
+ - `announced_ns`, `lost_ns`, `time_source` and related timestamp fields on
121
+ `participant_events` and `list_participants`.
122
+ - `health_check` reports `now_ns`, tracker status and an observed-domain note.
123
+ - `QosProfile` gains Liveliness, Ownership, Partition, LatencyBudget,
124
+ DestinationOrder and DataRepresentation.
125
+ - `peek_dds_samples` on builtin discovery topics returns structured endpoint
126
+ and participant fields instead of a raw repr string.
127
+ - Topic filters accept `rt/x` and `x` interchangeably; a filter that matches
128
+ nothing returns the known topics.
129
+ - `detect_qos_mismatches` also reports `matched` and `not_matched` pairs, hints
130
+ (near-miss topic names, path suffixes, type id differences), and the policies
131
+ it did and did not check.
132
+
133
+ ### Changed
134
+
135
+ - `detect_qos_mismatches` checks Partition first (with `*` and `?` wildcards),
136
+ then type names, then the RxO rules; Liveliness, LatencyBudget, Ownership,
137
+ DestinationOrder and DataRepresentation join Reliability, Durability and
138
+ Deadline.
139
+ - `topic_metrics` and `peek_dds_samples` on a user topic say they have no data
140
+ instead of returning zeros or a placeholder.
141
+ - Tool descriptions no longer carry internal history.
142
+
143
+ ### Fixed
144
+
145
+ - Infinite durations are `None` instead of 9223372036854775807.
146
+ - Races between the tracker and tool calls could mark a live participant lost
147
+ or keep endpoints of a departed one.
148
+ - Concurrent cyclonedds calls could corrupt the heap on Windows; every binding
149
+ call now takes one lock.
150
+ - A failing tracker no longer adds 3 s to every tool call.
151
+ - An unreadable QoS duration is treated as unknown, not infinite.
152
+ - Typo hints stay bounded on a bus with a thousand topics.
153
+
154
+ ### Known limits
155
+
156
+ - A hung writer (alive, no data) cannot be observed without a data probe.
157
+ - A crash and a clean leave look the same; `lost_ns` is an upper bound.
158
+ - A participant that cycles faster than the history depth between two tracker
159
+ passes can be missed.
160
+ - The Fast DDS backend has never run on a bus; `list_endpoints` is not
161
+ supported there.
162
+ - DDS Security is not supported, and user-topic payload decoding is disabled.
9
163
 
10
164
  ## [0.5.4] - 2026-10-02
11
165
 
12
- First run of the DDS code against a live multi-vendor bus (Windows 11, a
13
- Python / Cyclone DDS participant and a Rust / Dust DDS participant, TopicForge
14
- driven by a real MCP client). Until now every DDS adapter had only been
15
- checked statically. The run exposed four defects that together made the DDS
16
- module non-functional on Cyclone; all are fixed and pinned by tests.
166
+ First run of the DDS code against a live bus (Windows 11, a Python / Cyclone DDS
167
+ participant and a Rust / Dust DDS participant). It exposed four defects that
168
+ left the DDS module non-functional on Cyclone; all are fixed and tested.
17
169
 
18
170
  ### Fixed
19
171
 
20
- - Role nodes published below their rate (about 7 Hz for 10 Hz on Windows):
21
- they now keep an absolute schedule.
22
-
23
- - **`list_participants` now reports the participant name and the hostname on
24
- Cyclone.** The name comes from the EntityName QoS and the hostname from the
25
- `__Hostname` discovery property; both were always null on a real bus.
26
- - **Participant GUIDs were never read.** cyclonedds 11.0.1 exposes the builtin
27
- key as a `uuid.UUID`, which the extractor did not handle, so every
28
- participant collapsed onto a single `unknown` entry.
29
- - **Vendors were never identified.** The builtin participant sample carries no
30
- vendor field. The vendor id is now read from the first two bytes of the GUID
31
- prefix, as RTPS recommends; implementations that do not follow that
32
- convention (Dust DDS, and RTI by default) still report `unknown`, because
33
- the Cyclone Python binding does not expose the vendor id from the RTPS
34
- header.
35
- - **The OMG vendor-id table was wrong.** It mapped `01.05` to Fast DDS and
36
- `01.16` to Cyclone; the correct ids are `01.0F` (eProsima) and `01.10`
37
- (Eclipse), verified against both vendors' sources. The Cyclone side ran on a
38
- live bus; the Fast DDS side (`fast_extract_vendor_id`) is verified
39
- statically only, since its tests need a binding that is not on PyPI. The `vendor` field of
40
- `ParticipantInfo` and `ParticipantEvent` now also accepts `rti_micro`,
41
- `opensplice`, `opendds`, `coredx`, `intercom` and `dust` (soft-breaking for
42
- clients validating the previous enum).
43
- - **`detect_qos_mismatches` never reported anything on Cyclone.** cyclonedds
44
- scopes its policy class names (`Reliability.BestEffort`), and the
45
- normalizer matched only the bare name, so no QoS profile was ever built.
46
- - **A stopped participant never disappeared.** Discovery readers keep the last
47
- sample of a departed participant with a NOT_ALIVE instance state; those are
48
- now ignored for participants and endpoints, so a participant is reported as
49
- left when its lease expires, and a dead endpoint no longer produces a
50
- mismatch.
51
-
52
- - Cyclone adapter created a new DDS reader on a builtin discovery topic on
53
- every tool call and never deleted it. It now keeps one reader per builtin
54
- topic and takes a non-blocking snapshot, so calls no longer wait 2 s each
55
- (`detect_qos_mismatches` waited 4 s).
56
- - `peek_dds_samples` on `DCPSPublication` / `DCPSSubscription` reported the
57
- endpoints of participants that had left; disposed entries are now dropped.
58
- Its payload also carries the endpoint `type_name`.
59
- - `test_dds_cross_vendor.py` expected an error message from v0.3; it had
60
- never run, since CI has no DDS binding. First run against the real Cyclone
61
- binding.
172
+ - `list_participants` reports the participant name and hostname on Cyclone
173
+ (both were always null).
174
+ - Participant GUIDs were never read, so every participant collapsed onto one
175
+ `unknown` entry.
176
+ - Vendors were never identified. The vendor id is now read from the GUID prefix;
177
+ Dust DDS and RTI by default still report `unknown`.
178
+ - The OMG vendor-id table mapped Fast DDS and Cyclone to the wrong ids (now
179
+ `01.0F` and `01.10`). The Fast DDS side is verified statically only.
180
+ - `ParticipantInfo.vendor` also accepts `rti_micro`, `opensplice`, `opendds`,
181
+ `coredx`, `intercom` and `dust`.
182
+ - `detect_qos_mismatches` never reported anything on Cyclone because policy
183
+ class names were not normalized.
184
+ - A stopped participant never disappeared; departed participants and endpoints
185
+ are now dropped.
186
+ - The Cyclone adapter leaked a reader per tool call and waited 2 s each; it now
187
+ keeps one reader per builtin topic.
188
+ - `peek_dds_samples` on `DCPSPublication` / `DCPSSubscription` listed endpoints
189
+ of participants that had left, and now includes the endpoint `type_name`.
190
+ - Example role nodes published below their rate on Windows.
62
191
 
63
192
  ### Added
64
193
 
65
- - `examples/dds/`: a "write the code" track. Examples 00 (hello publisher
66
- and subscriber), 05 (Reliability), 06 (Durability and the late joiner),
67
- 07 (Deadline declared and kept) and 08 (a crash seen from inside and from
68
- outside) each ship a readable `publisher.py` and `subscriber.py` in Cyclone
69
- DDS Python; the subscriber prints what it receives and the DDS statuses,
70
- and TopicForge explains the same situation from outside. All fourteen
71
- examples pass on a live bus.
72
- - The generic role nodes print one line per second per reader with what
73
- they received, and examples 02, 04, 10 and 13 check that the broken
74
- subscriber receives nothing while the control one receives data.
75
- - The harness keeps each program's output in a log file and prints it live
76
- under `--hold`.
77
-
78
- - `scripts/integration/interop_check.py`: one-command multi-vendor demo
79
- that starts a Rust / Dust and a Python / Cyclone participant, drives
80
- TopicForge over stdio through the official MCP client, checks participant
81
- discovery, a deliberate Reliability mismatch between the two vendors, and
82
- the departure of a stopped participant, then stops every process it started.
83
- - A real Rust / Dust DDS participant (`publishers/dust_publisher`) and a real
84
- Python / Cyclone participant replacing the previous scaffold, which never
85
- wrote a sample.
86
- - Twelve interop programs in total, one per vendor and language with an
87
- officially released binding (Cyclone C / C++ / Rust / Python, Dust Rust /
88
- Python, Fast DDS C++ / Python, RTI Connext C / C++ / Python, OpenSplice C),
89
- all following the contract in `scripts/integration/DEMO_CONTRACT.md`. The
90
- driver starts whichever ones are built on the host and adapts its checks;
91
- `--list` shows what can run. Only Cyclone Python and Dust Rust / Python / C
92
- have been run, on Windows; the rest are written but unrun. RTI participants
93
- need a local license and are never run in CI.
94
- - One-command launch scripts, `scripts/integration/launch/setup` and
95
- `run_demo` (`.ps1` and `.sh`), which create `.venv-demo`, install the Cyclone
96
- binding and build the Rust participant. `setup.ps1 -Firewall` adds inbound
97
- UDP 7400-7500 rules on private networks for multi-machine runs.
98
- - Unicast peer configuration for Cyclone and Fast DDS and a guide for a mixed
99
- Linux and Windows bus (`scripts/integration/config/`).
100
- - `.github/workflows/demo.yml` runs the driver with the Cyclone and Dust
101
- participants on Ubuntu and Windows; `demo-fast.yml` builds Fast DDS 3 from
102
- pinned tags and starts the C++ participant (weekly and manual).
103
- - `scripts/integration/README.md` rewritten around the demo: it previously
104
- described the removed docker / scenario rig.
105
-
106
- - `examples/dds/`: real use cases 10 to 14 (driver swap, wiring, safety
107
- monitor dropout, deadline not offered, restart loop) next to the concept
108
- examples 01 to 04. All nine pass on a live bus.
194
+ - `examples/dds/`: fourteen runnable examples (concepts and use cases), all
195
+ passing on a live bus.
196
+ - `scripts/integration/`: a demo driver, twelve interop programs (Cyclone, Dust,
197
+ Fast DDS, RTI, OpenSplice), one-command setup scripts and CI workflows. Only
198
+ Cyclone Python and Dust Rust / Python / C have been run, on Windows.
109
199
 
110
200
  ### Removed
111
201
 
112
- - The docker / scenario integration rig (`scenarios_runner.py`, `run-local.*`,
113
- `docker-compose.yml`, per-vendor Dockerfiles, scenario JSON files, schema
114
- test and `integration.yml`) is removed in favour of the demo driver.
202
+ - The docker / scenario integration rig, replaced by the demo driver.
115
203
 
116
204
  ## [0.5.3] - 2026-10-01
117
205
 
@@ -1114,7 +1202,9 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
1114
1202
  - The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
1115
1203
  - `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
1116
1204
 
1117
- [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.4...HEAD
1205
+ [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.6...HEAD
1206
+ [0.5.6]: https://github.com/yaniswav/TopicForge/compare/v0.5.5...v0.5.6
1207
+ [0.5.5]: https://github.com/yaniswav/TopicForge/compare/v0.5.4...v0.5.5
1118
1208
  [0.5.4]: https://github.com/yaniswav/TopicForge/compare/v0.5.3...v0.5.4
1119
1209
  [0.5.3]: https://github.com/yaniswav/TopicForge/compare/v0.5.2...v0.5.3
1120
1210
  [0.5.2]: https://github.com/yaniswav/TopicForge/compare/v0.5.1...v0.5.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: topicforge
3
- Version: 0.5.4
3
+ Version: 0.5.6
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
@@ -45,7 +45,7 @@ Requires-Dist: pydantic>=2.6
45
45
  Provides-Extra: all
46
46
  Requires-Dist: cyclonedds>=0.10; extra == 'all'
47
47
  Provides-Extra: bags
48
- Requires-Dist: rosbags>=0.9; extra == 'bags'
48
+ Requires-Dist: rosbags>=0.11.3; extra == 'bags'
49
49
  Provides-Extra: dds
50
50
  Requires-Dist: cyclonedds>=0.10; extra == 'dds'
51
51
  Provides-Extra: dds-cyclone
@@ -53,7 +53,7 @@ Requires-Dist: cyclonedds>=0.10; extra == 'dds-cyclone'
53
53
  Provides-Extra: dev
54
54
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
55
55
  Requires-Dist: pytest>=8.0; extra == 'dev'
56
- Requires-Dist: rosbags>=0.9; extra == 'dev'
56
+ Requires-Dist: rosbags>=0.11.3; extra == 'dev'
57
57
  Requires-Dist: ruff>=0.4; extra == 'dev'
58
58
  Description-Content-Type: text/markdown
59
59
 
@@ -65,17 +65,17 @@ Description-Content-Type: text/markdown
65
65
  [![CI](https://github.com/yaniswav/TopicForge/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/yaniswav/TopicForge/actions/workflows/ci.yml)
66
66
  [![Python versions](https://img.shields.io/pypi/pyversions/topicforge.svg)](https://pypi.org/project/topicforge/)
67
67
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/yaniswav/TopicForge/blob/main/LICENSE)
68
- [![Read-only by architecture](https://img.shields.io/badge/safety-read--only_by_architecture-2563eb)](https://github.com/yaniswav/TopicForge#security-model)
68
+ [![Read-only](https://img.shields.io/badge/safety-read--only-2563eb)](https://github.com/yaniswav/TopicForge#security-model)
69
69
 
70
- A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a ROS2 graph, recorded bag files and the DDS layer underneath ROS, without being able to publish to the bus or command a robot. It is read-only by **architecture**, not by configuration: there is no write path to misconfigure and no permission system to audit.
70
+ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a ROS2 graph, recorded bag files and the DDS layer underneath ROS. The code has no write path: it cannot publish to the bus or command a robot, and there is no permission system to configure.
71
71
 
72
- Without grounding, an LLM asked about a robot will invent topic names, message types and bag contents. TopicForge gives it **eleven typed tools** that return frozen Pydantic schemas, identical whether the server talks to a real robot or to its built-in mock fixtures. It is aimed at ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
72
+ 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.
73
73
 
74
- 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. Every conformant vendor announces itself there, so a Cyclone participant also sees RTI Connext, OpenDDS, CoreDX and Dust DDS endpoints without any proprietary binding. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
74
+ 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).
75
75
 
76
76
  ## Quickstart
77
77
 
78
- No ROS2 needed; the mock adapter serves deterministic fixtures for a small differential robot (LIDAR + RGB camera). Python 3.10 to 3.13.
78
+ No ROS2 is needed; the mock adapter serves deterministic fixtures for a small differential robot (LIDAR + RGB camera). Python 3.10 to 3.13.
79
79
 
80
80
  ```bash
81
81
  pip install topicforge
@@ -101,7 +101,7 @@ Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code:
101
101
 
102
102
  ## Tools
103
103
 
104
- All eleven tools are read-only. Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
104
+ Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
105
105
 
106
106
  | Tool | Purpose |
107
107
  | ----------------------- | ------------------------------------------------------------------------------------------------ |
@@ -109,13 +109,14 @@ All eleven tools are read-only. Every response except `health_check` carries `mo
109
109
  | `list_topics` | Discover the ROS2 graph |
110
110
  | `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
111
111
  | `sample_messages` | Peek recent messages on a ROS2 topic (count clamped to 50) |
112
- | `analyze_bag` | Summarize a `.mcap` / `.db3` / `.bag` recording |
112
+ | `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
113
113
  | `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
114
114
  | `detect_qos_mismatches` | Incompatible QoS pairs between DDS readers and writers |
115
115
  | `peek_dds_samples` | Raw DDS samples; structured on the three builtin discovery topics, presence-only on user topics |
116
116
  | `participant_events` | Timeline of participant `discovered` / `lost` events |
117
117
  | `topic_metrics` | Frequency, sequence-gap and latency schema; data only for builtin discovery topics |
118
- | `peek_bag_samples` | Decoded samples from a recorded bag (needs `pip install topicforge[bags]`) |
118
+ | `peek_bag_samples` | Decoded samples from a recorded bag, including ROS 1 `.bag` (needs `pip install topicforge[bags]`) |
119
+ | `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
119
120
 
120
121
  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`).
121
122
 
@@ -136,9 +137,9 @@ pip install topicforge[dds] # Eclipse CycloneDDS ([dds-cycl
136
137
  TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
137
138
  ```
138
139
 
139
- `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). 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 five DDS tools to the DDS backend.
140
+ `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.
140
141
 
141
- 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, since the Pro tier is retired (see [`docs/pro.md`](docs/pro.md)). 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).
142
+ 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).
142
143
 
143
144
  ## Configuration reference
144
145
 
@@ -148,55 +149,59 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
148
149
  | `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
149
150
  | `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
150
151
  | `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
151
- | `TOPICFORGE_DDS_BACKEND` | `mock` | `mock`, `cyclone`, `fast`, `auto` (`opendds` and `dust` are stubs) |
152
+ | `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto` (`opendds` and `dust` are stubs) |
152
153
  | `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
154
+ | `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 |
153
155
 
154
156
  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.
155
157
 
156
158
  ## Limitations
157
159
 
158
- - **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.
159
- - **User-topic payloads are not decoded.** `peek_dds_samples` on a user topic reports that the topic is announced on the bus and returns one placeholder sample (`_decode_status="raw"`, empty `_raw_bytes_hex`); no traffic is read. Consequently `topic_metrics` only has data for the builtin discovery topics, its observed frequency is the cadence of your own `peek_dds_samples` calls, and latency and sequence gaps are `null`. It is a discovery-layer probe, not a publish-rate monitor.
160
- - **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`.
161
- - **DDS Security is not handled.** A participant without credentials sees an empty secure bus. `detect_qos_mismatches` covers Reliability, Durability, History and Deadline; Liveliness, Ownership and Partition are not checked.
162
- - **`sample_messages` (live)** runs `ros2 topic echo --csv --once` with a short timeout; a topic with no current publisher returns an empty sample. `timestamp_ns` is the message `header.stamp` for `Header`-stamped types and `0` for headerless ones.
163
- - **`analyze_bag` (live)** parses `ros2 bag info` text and does not use `rosbags`; anomaly detection is mock-only. `peek_bag_samples` is the only tool that reads the file itself, through `rosbags`, and is served only by the ROS2 CLI adapter or the mock. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output.
164
- - **Synchronous handlers.** The tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
160
+ - 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.
161
+ - 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.
162
+ - 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 for 0.5.6. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
163
+ - 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`.
164
+ - Single domain: the server observes the domain it joined at startup; changing it needs a restart.
165
+ - 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.
166
+ - Fast DDS serves no `list_endpoints`.
167
+ - `sample_messages` (live) runs `ros2 topic echo --csv --once` with a short timeout, so it returns at most one message, and a topic with no current publisher returns an empty sample. `timestamp_ns` is the message `header.stamp` for `Header`-stamped types and `0` for headerless ones. 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_after_columns`.
168
+ - `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`, and is served only by the ROS2 CLI adapter or the mock; 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.
169
+ - Synchronous handlers: the tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
165
170
  - No streaming or push subscriptions: tools are strictly request/response.
166
171
 
167
- The roadmap and the open work behind these limits are in [`docs/product-plan.md`](docs/product-plan.md).
172
+ 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).
168
173
 
169
174
  ## Telemetry
170
175
 
171
- Opt-in, anonymous and **off by default**. When off, instrumentation returns the handler unchanged: no event is built, no transport is constructed, no network code runs (pinned by `tests/test_telemetry.py::test_build_app_off_makes_no_transport_calls`).
176
+ Opt-in, anonymous and off by default. When off, instrumentation returns the handler unchanged: no event is built, no transport is constructed, no network code runs (pinned by `tests/test_telemetry.py::test_build_app_off_makes_no_transport_calls`).
172
177
 
173
178
  ```bash
174
179
  TOPICFORGE_TELEMETRY=on python -m topicforge
175
180
  ```
176
181
 
177
- On-values: `on`, `1`, `true`, `yes`, `enabled`. Off-values: unset, `off`, `0`, `false`, `no`, `disabled`. Anything else is a configuration error, not a silent "off".
182
+ On-values: `on`, `1`, `true`, `yes`, `enabled`. Off-values: unset, `off`, `0`, `false`, `no`, `disabled`. Anything else is a configuration error rather than a silent "off".
178
183
 
179
184
  When on, each tool call emits one event with exactly six fields:
180
185
 
181
186
  | Field | Example | Notes |
182
187
  | ------------ | --------------- | ----------------------------------------------------------- |
183
- | `tool_name` | `"list_topics"` | One of the eleven tools, never argument values |
188
+ | `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
184
189
  | `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
185
190
  | `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
186
- | `version` | `"0.5.3"` | TopicForge server version |
191
+ | `version` | `"0.5.6"` | TopicForge server version |
187
192
  | `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
188
193
  | `success` | `true` | Whether the handler returned or raised |
189
194
 
190
- Never sent: topic names, message types or payloads, bag paths or contents, hostnames, usernames, IP addresses, environment variables, error messages. The field set is fenced by `tests/test_telemetry.py::test_payload_contains_only_whitelisted_keys`; adding a field requires updating this section. The default transport is a structured log line, there is no HTTP endpoint yet. The implementation is in [`src/topicforge/telemetry/`](src/topicforge/telemetry/).
195
+ Never sent: topic names, message types or payloads, bag paths or contents, hostnames, usernames, IP addresses, environment variables, error messages. The field set is fenced by `tests/test_telemetry.py::test_payload_contains_only_whitelisted_keys`; adding a field requires updating this section. The default transport is a structured log line; there is no HTTP endpoint yet. The implementation is in [`src/topicforge/telemetry/`](src/topicforge/telemetry/).
191
196
 
192
197
  ## Security model
193
198
 
194
- TopicForge is designed for **local trust**: it runs as a subprocess of your MCP client on a machine you control and inspects your own ROS2 graph, DDS domain and bag files. It is not hardened for adversarial inputs.
199
+ TopicForge is designed for local trust: it runs as a subprocess of your MCP client on a machine you control and inspects your own ROS2 graph, DDS domain and bag files. It is not hardened for adversarial inputs.
195
200
 
196
201
  - `TOPICFORGE_ROS2_BIN` accepts an arbitrary path; treat it the way you treat `PATH`.
197
202
  - `analyze_bag` and `peek_bag_samples` open whatever path the client passes (no workspace isolation, no symlink restriction).
198
203
  - All `ros2` invocations use `subprocess.run` with an argument list, never `shell=True`. ROS2 topic names are validated against `^/[A-Za-z0-9_/]+$` first.
199
- - The server loads no third-party code at startup. Until 0.5.2 it imported any installed `topicforge_pro` package; that hook was removed in 0.5.3 because it was an opening for a package of that name to add write tools.
204
+ - The server loads no third-party code at startup.
200
205
  - No outbound network calls unless telemetry is turned on.
201
206
 
202
207
  Before exposing TopicForge to untrusted MCP clients (hosted endpoints, shared environments), add path isolation and revisit the `TOPICFORGE_ROS2_BIN` policy. Vulnerability reports: see [`SECURITY.md`](SECURITY.md).
@@ -215,7 +220,7 @@ Tests run against the mock adapter, the live adapter's pure parsers and the bind
215
220
 
216
221
  ## Upgrading
217
222
 
218
- TopicForge is pre-1.0 and the 0.x releases changed things freely; [`CHANGELOG.md`](CHANGELOG.md) is the record. Two points matter if you are coming from an old install. Releases 0.3.0 to 0.5.2 are yanked, so `pip install -U topicforge` resolves to 0.5.3 or later. And since 0.5.3 the `[dds-fast]`, `[dds-opendds]`, `[dds-dust]` and `[dds-all-oss]` extras no longer exist, the DDS backend values `rti`, `opensplice`, `coredx` and `intercom` are rejected, and `[dds]` and `[all]` resolve to Cyclone only. Schema changes across 0.x were additive optional fields; a client that pins a JSON Schema with `additionalProperties: false` needs to regenerate it.
223
+ TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras.
219
224
 
220
225
  ## Layout
221
226
 
@@ -238,4 +243,4 @@ Layers are strictly separated: handlers never call `subprocess`, adapters are th
238
243
 
239
244
  ## License
240
245
 
241
- MIT, see [LICENSE](LICENSE). Commercial support and integration work: [`docs/pro.md`](docs/pro.md).
246
+ MIT, see [LICENSE](LICENSE). Integration or support work for a specific ROS 2 / DDS setup: ethvignot.yanis@gmail.com.