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.
- {topicforge-0.5.4 → topicforge-0.5.6}/.gitignore +7 -36
- {topicforge-0.5.4 → topicforge-0.5.6}/CHANGELOG.md +186 -96
- {topicforge-0.5.4 → topicforge-0.5.6}/PKG-INFO +36 -31
- {topicforge-0.5.4 → topicforge-0.5.6}/README.md +33 -28
- topicforge-0.5.6/docs/DDS_QUICKSTART.md +126 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/docs/TESTING.md +7 -7
- {topicforge-0.5.4 → topicforge-0.5.6}/docs/TROUBLESHOOTING.md +11 -11
- {topicforge-0.5.4 → topicforge-0.5.6}/docs/TUTORIEL.md +17 -21
- topicforge-0.5.6/docs/dds-interop-matrix.md +55 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/README.md +3 -3
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/00_hello_pub_sub/README.md +17 -23
- topicforge-0.5.6/examples/dds/01_who_is_on_the_bus/README.md +47 -0
- topicforge-0.5.6/examples/dds/02_why_cant_they_talk/README.md +60 -0
- topicforge-0.5.6/examples/dds/03_a_node_crashed/README.md +56 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/04_late_joiner_misses_data/README.md +14 -17
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/05_reliability_in_code/README.md +14 -16
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/06_durability_late_joiner_in_code/README.md +24 -30
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/07_deadline_in_code/README.md +15 -22
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/08_crash_seen_from_inside/README.md +24 -31
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/10_lidar_silent_after_driver_swap/README.md +20 -19
- topicforge-0.5.6/examples/dds/11_who_talks_to_whom/README.md +50 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/12_safety_monitor_dropout/README.md +17 -25
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/13_deadline_not_offered/README.md +18 -23
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/14_restart_loop/README.md +16 -19
- {topicforge-0.5.4 → topicforge-0.5.6}/examples/dds/README.md +13 -9
- {topicforge-0.5.4 → topicforge-0.5.6}/pyproject.toml +4 -4
- topicforge-0.5.6/scripts/agent_eval/README.md +38 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/README.md +12 -14
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/fast_publisher_cpp/README.md +2 -2
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/fast_py/README.md +3 -3
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/opensplice_publisher/README.md +2 -2
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/__init__.py +1 -1
- topicforge-0.5.6/src/topicforge/adapters/base.py +131 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/__init__.py +67 -1
- topicforge-0.5.6/src/topicforge/adapters/common/cdr_decoder.py +176 -0
- topicforge-0.5.6/src/topicforge/adapters/common/dds_helpers.py +237 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/dds_introspection.py +18 -36
- topicforge-0.5.6/src/topicforge/adapters/common/discovery_tracker.py +491 -0
- topicforge-0.5.6/src/topicforge/adapters/common/endpoints.py +395 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/lifecycle.py +68 -76
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/common/metrics_buffer.py +36 -92
- topicforge-0.5.6/src/topicforge/adapters/common/qos_analyzer.py +313 -0
- topicforge-0.5.6/src/topicforge/adapters/common/qos_endpoints.py +75 -0
- topicforge-0.5.6/src/topicforge/adapters/common/qos_normalize.py +308 -0
- topicforge-0.5.6/src/topicforge/adapters/common/qos_scan.py +423 -0
- topicforge-0.5.6/src/topicforge/adapters/common/topic_filter.py +106 -0
- topicforge-0.5.6/src/topicforge/adapters/common/xtypes.py +97 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/composite.py +50 -27
- topicforge-0.5.6/src/topicforge/adapters/dds_cyclone/adapter.py +559 -0
- topicforge-0.5.6/src/topicforge/adapters/dds_dust/__init__.py +10 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_dust/adapter.py +33 -24
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_fast/adapter.py +85 -165
- topicforge-0.5.6/src/topicforge/adapters/dds_opendds/__init__.py +11 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_opendds/adapter.py +37 -34
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_live/adapter.py +205 -138
- topicforge-0.5.6/src/topicforge/adapters/ros2_live/parsers.py +113 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/adapter.py +30 -23
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/fixtures.py +158 -104
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/config/settings.py +55 -67
- topicforge-0.5.6/src/topicforge/constants.py +25 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/models/__init__.py +16 -0
- topicforge-0.5.6/src/topicforge/models/schemas.py +1355 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/server/app.py +6 -16
- topicforge-0.5.6/src/topicforge/services/bag_service.py +412 -0
- topicforge-0.5.6/src/topicforge/services/bag_stats.py +218 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/services/factory.py +83 -55
- topicforge-0.5.6/src/topicforge/services/health.py +112 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/services/inspector.py +110 -51
- topicforge-0.5.6/src/topicforge/services/sample_budget.py +53 -0
- topicforge-0.5.6/src/topicforge/telemetry/__init__.py +22 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/telemetry/client.py +19 -41
- topicforge-0.5.6/src/topicforge/tools/handlers.py +639 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/conftest.py +16 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +20 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +104 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_scan.txt +20 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_tf.txt +34 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +34 -0
- topicforge-0.5.6/tests/integration/ros2/Dockerfile +28 -0
- topicforge-0.5.6/tests/integration/ros2/__init__.py +0 -0
- topicforge-0.5.6/tests/integration/ros2/entrypoint.sh +43 -0
- topicforge-0.5.6/tests/integration/ros2/publisher.py +112 -0
- topicforge-0.5.6/tests/integration/ros2/run_bench.py +79 -0
- topicforge-0.5.6/tests/integration/ros2/test_live_adapter.py +191 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_analyze_bag_multi_format.py +5 -10
- topicforge-0.5.6/tests/test_bag_omnisim_humble.py +150 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_bag_service.py +1 -3
- topicforge-0.5.6/tests/test_bag_stats.py +291 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_cdr_decoder.py +34 -13
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_composite_adapter.py +13 -10
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_config.py +7 -7
- topicforge-0.5.6/tests/test_cyclone_adapter.py +226 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_cross_vendor.py +6 -11
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_helpers.py +36 -9
- topicforge-0.5.6/tests/test_dds_inactive_reason.py +94 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_introspection.py +4 -5
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_qos_normalization.py +48 -6
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dds_schemas.py +1 -1
- topicforge-0.5.6/tests/test_discovery_tracker.py +462 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_dust_adapter.py +3 -4
- topicforge-0.5.6/tests/test_endpoints.py +444 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_example_node_spec.py +72 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_factory.py +5 -4
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_fast_adapter.py +3 -3
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_health.py +22 -0
- topicforge-0.5.6/tests/test_history_and_hints.py +273 -0
- topicforge-0.5.6/tests/test_honest_outputs.py +250 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_inspector.py +1 -1
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_lifecycle_buffer.py +2 -2
- topicforge-0.5.6/tests/test_live_adapter_graph.py +404 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_metrics_buffer.py +7 -9
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_mock_adapter.py +23 -7
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_opendds_adapter.py +9 -9
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_peek_bag_samples.py +1 -1
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_qos_analyzer.py +208 -6
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_qos_endpoints.py +18 -13
- topicforge-0.5.6/tests/test_qos_scan.py +386 -0
- topicforge-0.5.6/tests/test_sample_options.py +217 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_telemetry.py +3 -3
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_tools_integration.py +15 -11
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_topic_metrics.py +1 -1
- topicforge-0.5.4/docs/DDS_QUICKSTART.md +0 -121
- topicforge-0.5.4/docs/dds-interop-matrix.md +0 -58
- topicforge-0.5.4/docs/pro.md +0 -38
- topicforge-0.5.4/docs/product-plan.md +0 -183
- topicforge-0.5.4/examples/dds/01_who_is_on_the_bus/README.md +0 -58
- topicforge-0.5.4/examples/dds/02_why_cant_they_talk/README.md +0 -61
- topicforge-0.5.4/examples/dds/03_a_node_crashed/README.md +0 -61
- topicforge-0.5.4/examples/dds/11_who_talks_to_whom/README.md +0 -53
- topicforge-0.5.4/src/topicforge/adapters/base.py +0 -134
- topicforge-0.5.4/src/topicforge/adapters/common/cdr_decoder.py +0 -203
- topicforge-0.5.4/src/topicforge/adapters/common/dds_helpers.py +0 -202
- topicforge-0.5.4/src/topicforge/adapters/common/qos_analyzer.py +0 -98
- topicforge-0.5.4/src/topicforge/adapters/common/qos_endpoints.py +0 -95
- topicforge-0.5.4/src/topicforge/adapters/common/qos_normalize.py +0 -182
- topicforge-0.5.4/src/topicforge/adapters/common/xtypes.py +0 -120
- topicforge-0.5.4/src/topicforge/adapters/dds_cyclone/adapter.py +0 -651
- topicforge-0.5.4/src/topicforge/adapters/dds_dust/__init__.py +0 -12
- topicforge-0.5.4/src/topicforge/adapters/dds_opendds/__init__.py +0 -15
- topicforge-0.5.4/src/topicforge/constants.py +0 -28
- topicforge-0.5.4/src/topicforge/models/schemas.py +0 -706
- topicforge-0.5.4/src/topicforge/services/bag_service.py +0 -273
- topicforge-0.5.4/src/topicforge/services/health.py +0 -82
- topicforge-0.5.4/src/topicforge/telemetry/__init__.py +0 -27
- topicforge-0.5.4/src/topicforge/tools/handlers.py +0 -453
- topicforge-0.5.4/tests/test_cyclone_adapter.py +0 -120
- {topicforge-0.5.4 → topicforge-0.5.6}/LICENSE +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/__main__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/fixtures/csv_echo_imu.txt +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/fixtures/csv_echo_pose_multi.txt +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/integration/__init__.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.5.4 → topicforge-0.5.6}/tests/test_live_adapter_subprocess.py +0 -0
- {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
|
-
|
|
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
|
-
## [
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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/`:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
[](https://github.com/yaniswav/TopicForge/actions/workflows/ci.yml)
|
|
66
66
|
[](https://pypi.org/project/topicforge/)
|
|
67
67
|
[](https://github.com/yaniswav/TopicForge/blob/main/LICENSE)
|
|
68
|
-
[](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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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).
|
|
246
|
+
MIT, see [LICENSE](LICENSE). Integration or support work for a specific ROS 2 / DDS setup: ethvignot.yanis@gmail.com.
|