topicforge 0.8.1__tar.gz → 0.9.0__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.8.1 → topicforge-0.9.0}/CHANGELOG.md +44 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/PKG-INFO +15 -15
- {topicforge-0.8.1 → topicforge-0.9.0}/README.md +14 -14
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/CLIENTS.md +17 -17
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/CONTRACT.md +103 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/DDS_QUICKSTART.md +18 -19
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/EVAL.md +75 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/TESTING.md +6 -6
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/TOOLS.md +63 -29
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/TROUBLESHOOTING.md +8 -13
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/TUTORIEL.md +4 -4
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/VALIDATION.md +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/dds-interop-matrix.md +3 -3
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/README.md +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/plugin/README.md +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/plugin/pyproject.toml +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/pyproject.toml +7 -11
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/agent_eval/README.md +3 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/ground_truth/README.md +1 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/README.md +7 -7
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/__init__.py +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/dds_helpers.py +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/dds_introspection.py +2 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/discovery_tracker.py +20 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/lifecycle.py +5 -5
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_endpoints.py +4 -4
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_normalize.py +4 -3
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/xtypes.py +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/composite.py +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/adapter.py +1 -7
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/adapter.py +2 -3
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/config/settings.py +10 -6
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/constants.py +6 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/models/__init__.py +5 -0
- topicforge-0.9.0/src/topicforge/models/diagnose.py +107 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/models/nodes.py +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/models/schemas.py +43 -50
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/models/summaries.py +4 -5
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/server/prompts.py +3 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/dds_views.py +1 -1
- topicforge-0.9.0/src/topicforge/services/diagnose.py +672 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/factory.py +2 -30
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/health.py +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/inspector.py +5 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/tools/guard.py +3 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/tools/handlers.py +61 -24
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/_prompts.json +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/analyze_bag.json +3 -3
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/detect_qos_mismatches.json +4 -4
- topicforge-0.9.0/tests/contract/diagnose.json +262 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/get_topic_info.json +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/graph_diff.json +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/health_check.json +4 -4
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/inspect_tf.json +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/list_endpoints.json +5 -5
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/list_nodes.json +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/list_participants.json +5 -5
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/list_topics.json +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/participant_events.json +3 -3
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/peek_bag_samples.json +5 -5
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/peek_dds_samples.json +6 -6
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/sample_messages.json +6 -6
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/topic_metrics.json +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/entrypoint.sh +4 -3
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/ground_truth.py +43 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_agent_eval_tools.py +29 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_config.py +13 -31
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_contract_shape.py +2 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_dds_cross_vendor.py +15 -39
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_dds_inactive_reason.py +0 -6
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_dds_introspection.py +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_dds_qos_normalization.py +1 -1
- topicforge-0.9.0/tests/test_diagnose.py +761 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_endpoints.py +52 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_factory.py +2 -16
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_friction_fixes.py +27 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_ground_truth_compare.py +243 -3
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_honest_outputs.py +1 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_http_transport.py +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_output_schema_validation.py +1 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_tool_annotations.py +2 -2
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_tool_descriptions.py +3 -1
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_tools_integration.py +5 -3
- topicforge-0.8.1/src/topicforge/adapters/dds_fast/__init__.py +0 -11
- topicforge-0.8.1/src/topicforge/adapters/dds_fast/adapter.py +0 -487
- topicforge-0.8.1/tests/test_fast_adapter.py +0 -149
- {topicforge-0.8.1 → topicforge-0.9.0}/.gitignore +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/LICENSE +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/docs/MIGRATION-0.7.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/00_hello_pub_sub/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/02_why_cant_they_talk/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/03_a_node_crashed/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/05_reliability_in_code/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/07_deadline_in_code/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/11_who_talks_to_whom/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/13_deadline_not_offered/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/14_restart_loop/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/examples/dds/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/plugin/LICENSE +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/fast_py/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/__main__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/base.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/bag_kind.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/bag_range.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/endpoints.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/log_filter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/nodes.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/param_diff.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_fixes.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_scan.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/ros_discovery.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/ros_names.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/spawned_pids.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/stamps.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/tf_tree.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/common/urdf.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/ros_info.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/echo_stream.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/node_parsers.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/process_runner.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/sample_extras.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/tf_capture.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/tf_parsers.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/adapter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/fixtures.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/graph_fixtures.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/node_fixtures.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/tf_fixtures.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/budget.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/models/graph_diff.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/models/tf.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/server/app.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/server/http.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/bag_service.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/bag_stats.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/graph_diff.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/graph_snapshot.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/params_file.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/sample_budget.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/summaries/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/summaries/_common.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/summaries/clock.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/summaries/laser_scan.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/summaries/motion.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/summaries/rate.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/services/summaries/sensor_data.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/tools/annotations.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/src/topicforge/tools/schema_compact.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/conftest.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/contract/get_node_info.json +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_064_results.json +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_064_truth.json +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_morning_results.json +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_morning_truth.json +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_rerun_results.json +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_rerun_truth.json +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_echo/twist.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_humble.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_no_param_services.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_not_found.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_duplicates.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_plain.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_warning_stdout.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/param_dump_humble.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/param_dump_secrets.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_node/service_list_types.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_clock.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros_discovery/humble_two_nodes.hex +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/fixtures/ros_discovery/jazzy_two_nodes.hex +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/Dockerfile +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/__init__.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/bench_params.yaml +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/blocked_node.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/fault_nodes.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/faults.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/graph_change.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/lifecycle_node.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/publisher.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/run_bench.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/sros2_check.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/ros2/test_live_adapter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_analyze_bag_multi_format.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_bag_omnisim_humble.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_bag_service.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_bag_stats.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_bag_summaries.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_cdr_decoder.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_clock_basis.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_composite_adapter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_composite_ghosts.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_contract_snapshots.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_contract_v2.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_cyclone_adapter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_cyclone_ros_info.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_dds_helpers.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_dds_schemas.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_discovery_tracker.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_echo_parser.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_echo_stream.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_field_names.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_graph_diff.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_health.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_history_and_hints.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_inspector.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_lifecycle_buffer.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_live_adapter_graph.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_live_sample_notes.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_log_filter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_metrics_buffer.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_mock_adapter.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_node_logic.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_node_parsers.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_node_tools.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_param_diff.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_process_runner.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_process_tree.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_prompts.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_qos_endpoints.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_qos_fixes.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_qos_scan.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_rate_verdicts.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_restarts_by_node.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_ros_discovery.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_sample_options.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_sample_summary_flow.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_server_guard.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_server_info_version.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_server_lanes.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_stamps.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_summaries.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_summary_schema_compat.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_telemetry.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_tf_tools.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_tf_tree.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_topic_metrics.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_urdf.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_version_consistency.py +0 -0
- {topicforge-0.8.1 → topicforge-0.9.0}/tests/test_xtypes.py +0 -0
|
@@ -5,6 +5,48 @@ 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]
|
|
9
|
+
|
|
10
|
+
## [0.9.0] - 2026-11-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `diagnose`, the seventeenth tool: one bounded sweep of the ROS 2 graph and the DDS bus that
|
|
15
|
+
answers "is anything wrong". It runs `environment`, `nodes`, `topics`, `dds`, `tf`, `logs` and
|
|
16
|
+
`responsiveness` (a parameter read per node, the `node` argument first) from the existing
|
|
17
|
+
services, within `budget_s` (10..40, default 30), and returns findings ranked `critical`,
|
|
18
|
+
`warning`, `info` with evidence and, for QoS, the `suggested_fixes` of `detect_qos_mismatches`.
|
|
19
|
+
A check that errors or runs out of budget is listed in `checks_skipped` and the verdict then
|
|
20
|
+
says the sweep is incomplete: it never reads as an all-clear. It holds the ROS lock for up to
|
|
21
|
+
`budget_s`. `tools/list` is 99,634 bytes (`diagnose` 4,267) against the 105,000 ceiling. The
|
|
22
|
+
bench gains `50_diagnose` and `51_diagnose_node` and a comparator section that requires the
|
|
23
|
+
exact set of seeded findings (`docs/CONTRACT.md` section 9.21).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `tools/list` is 95,391 bytes (-1,924): shorter lane notes, no duplicated "Read-only" phrase,
|
|
28
|
+
and every output field description at most 120 characters, with a test that keeps it so. The
|
|
29
|
+
`tools/list` test ceiling moves from 100,000 to 105,000 bytes to make room for `diagnose`
|
|
30
|
+
(`docs/CONTRACT.md` section 9.20).
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
34
|
+
- The fastdds-based DDS adapter (`adapters/dds_fast/`, 487 lines). It never ran against a bus,
|
|
35
|
+
and its `fastdds` binding is not on PyPI, so no user could install it. Fast DDS participants
|
|
36
|
+
are still observed through the `cyclone` backend by standard RTPS discovery, as the CI bench
|
|
37
|
+
and the OmniSim kit show. `TOPICFORGE_DDS_BACKEND=fast` is now refused at startup with that
|
|
38
|
+
route, and `auto` only probes `cyclonedds`. `health_check.dds_backend` keeps `fast` in its
|
|
39
|
+
schema enum but never emits it. `demo-fast.yml` (a C++ Fast DDS publisher seen by Cyclone)
|
|
40
|
+
stays.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `detect_qos_mismatches` on a live DDS backend now fills `reader_node` and `writer_node`. 0.8.1
|
|
45
|
+
built the scan's endpoints without the gid -> node map that `list_endpoints` uses, so both
|
|
46
|
+
fields stayed `null` on every pair (seen on Fast DDS in the OmniSim kit). Both tools now build
|
|
47
|
+
their endpoints through one snapshot method. The Docker bench checks the writer node on the
|
|
48
|
+
scan's pairs (`docs/CONTRACT.md` section 9.19).
|
|
49
|
+
|
|
8
50
|
## [0.8.1] - 2026-10-26
|
|
9
51
|
|
|
10
52
|
The 0.8.0 evaluation findings, a URDF read from the robot_state_publisher parameter, an optional
|
|
@@ -1901,7 +1943,8 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1901
1943
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1902
1944
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1903
1945
|
|
|
1904
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.
|
|
1946
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.9.0...HEAD
|
|
1947
|
+
[0.9.0]: https://github.com/yaniswav/TopicForge/compare/v0.8.1...v0.9.0
|
|
1905
1948
|
[0.8.1]: https://github.com/yaniswav/TopicForge/compare/v0.8.0...v0.8.1
|
|
1906
1949
|
[0.8.0]: https://github.com/yaniswav/TopicForge/compare/v0.7.2...v0.8.0
|
|
1907
1950
|
[0.7.2]: https://github.com/yaniswav/TopicForge/compare/v0.7.1...v0.7.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: topicforge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
@@ -73,7 +73,7 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
73
73
|
|
|
74
74
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
75
75
|
|
|
76
|
-
It gives the agent
|
|
76
|
+
It gives the agent seventeen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
77
77
|
|
|
78
78
|
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
79
79
|
|
|
@@ -103,7 +103,7 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
103
103
|
|
|
104
104
|
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback (another `--host` needs a bearer token in `TOPICFORGE_HTTP_TOKEN`); see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
105
105
|
|
|
106
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
106
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.9.0%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
107
107
|
|
|
108
108
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
109
109
|
|
|
@@ -120,6 +120,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
120
120
|
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
121
121
|
| `inspect_tf` | The TF tree from `/tf_static` and `/tf`: roots, stale and two-parent frames, disconnected trees, sim-time-safe ages and rates, optional path between two frames |
|
|
122
122
|
| `graph_diff` | What changed since it last worked: snapshots the ROS 2 graph (or a bag) and diffs it with `previous`, a `snap-N` id or a bag: nodes, topics, types, writer QoS, counts, bag rates |
|
|
123
|
+
| `diagnose` | Is anything wrong: one bounded sweep (nodes, topics, QoS pairs, restarts, TF, `/rosout` errors, a parameter read per node) ranked `critical` / `warning` / `info` with evidence; a check that cannot run is listed, never silent. Holds the ROS lock up to `budget_s` |
|
|
123
124
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
124
125
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
125
126
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -152,9 +153,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
152
153
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
153
154
|
```
|
|
154
155
|
|
|
155
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone
|
|
156
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone` and `auto` (`cyclone` if the binding imports, else `mock`). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the nine `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `diagnose`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` goes to the ROS half but runs no `ros2` process.
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
Fast DDS participants (ROS 2 nodes on `rmw_fastrtps_cpp`) need no backend of their own: the Cyclone backend observes them through standard RTPS discovery. Since 0.9.0 `TOPICFORGE_DDS_BACKEND=fast` is refused, like `rti`, `opensplice`, `coredx`, `intercom`, `opendds` and `dust`: startup stops with an error that lists the valid values (see [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md)). Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md).
|
|
158
159
|
|
|
159
160
|
## Configuration reference
|
|
160
161
|
|
|
@@ -164,7 +165,7 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
164
165
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
165
166
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
|
|
166
167
|
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
|
|
167
|
-
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `
|
|
168
|
+
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `auto`; anything else is rejected |
|
|
168
169
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
169
170
|
| `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 |
|
|
170
171
|
|
|
@@ -176,19 +177,18 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
176
177
|
|
|
177
178
|
External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
178
179
|
|
|
179
|
-
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`).
|
|
180
|
+
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). 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.
|
|
180
181
|
- 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.
|
|
181
182
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
182
183
|
- 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`.
|
|
183
184
|
- Single domain: the server observes the domain it joined at startup; changing it needs a restart.
|
|
184
185
|
- 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.
|
|
185
|
-
- Fast DDS serves no `list_endpoints`.
|
|
186
186
|
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
187
187
|
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
188
188
|
- Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
|
|
189
189
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
Not done yet: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (RTI, OpenDDS) and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
|
|
192
192
|
|
|
193
193
|
## Telemetry
|
|
194
194
|
|
|
@@ -204,10 +204,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
204
204
|
|
|
205
205
|
| Field | Example | Notes |
|
|
206
206
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
207
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
207
|
+
| `tool_name` | `"list_topics"` | One of the seventeen tools, never argument values |
|
|
208
208
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
209
209
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
210
|
-
| `version` | `"0.
|
|
210
|
+
| `version` | `"0.9.0"` | TopicForge server version |
|
|
211
211
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
212
212
|
| `success` | `true` | Whether the handler returned or raised |
|
|
213
213
|
|
|
@@ -235,7 +235,7 @@ python -m ruff check src tests
|
|
|
235
235
|
python -m pytest
|
|
236
236
|
```
|
|
237
237
|
|
|
238
|
-
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds`
|
|
238
|
+
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds` binding skip themselves when it is absent, and the `integration` marker (real-bus tests) is deselected by default. The `Makefile` (`make check`) uses POSIX shell syntax; on plain PowerShell run the commands above. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
239
239
|
|
|
240
240
|
## Upgrading
|
|
241
241
|
|
|
@@ -248,13 +248,13 @@ src/topicforge/
|
|
|
248
248
|
server/ MCP bootstrap, build_app(settings), prompts and instructions
|
|
249
249
|
tools/ thin MCPServer handlers, no backend logic
|
|
250
250
|
services/ input validation, orchestration, adapter factory
|
|
251
|
-
adapters/ ros2_live, ros2_mock, dds_cyclone,
|
|
251
|
+
adapters/ ros2_live, ros2_mock, dds_cyclone, common/ (binding-free logic)
|
|
252
252
|
models/ frozen Pydantic schemas, the contract with MCP clients
|
|
253
253
|
config/ settings and mode resolution
|
|
254
254
|
telemetry/ opt-in, off by default
|
|
255
255
|
examples/ mock walkthroughs (*.md) and runnable live DDS examples (dds/)
|
|
256
|
-
scripts/ real-bus interop checks
|
|
257
|
-
docs/ guides
|
|
256
|
+
scripts/ real-bus interop checks, ground-truth comparator, agent evaluation
|
|
257
|
+
docs/ guides and reference
|
|
258
258
|
tests/ pytest suite, mock-only, no ROS2 or DDS required
|
|
259
259
|
```
|
|
260
260
|
|
|
@@ -12,7 +12,7 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
12
12
|
|
|
13
13
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
14
14
|
|
|
15
|
-
It gives the agent
|
|
15
|
+
It gives the agent seventeen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
16
16
|
|
|
17
17
|
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
18
18
|
|
|
@@ -42,7 +42,7 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
42
42
|
|
|
43
43
|
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback (another `--host` needs a bearer token in `TOPICFORGE_HTTP_TOKEN`); see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
44
44
|
|
|
45
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
45
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.9.0%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
46
46
|
|
|
47
47
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
48
48
|
|
|
@@ -59,6 +59,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
59
59
|
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
60
60
|
| `inspect_tf` | The TF tree from `/tf_static` and `/tf`: roots, stale and two-parent frames, disconnected trees, sim-time-safe ages and rates, optional path between two frames |
|
|
61
61
|
| `graph_diff` | What changed since it last worked: snapshots the ROS 2 graph (or a bag) and diffs it with `previous`, a `snap-N` id or a bag: nodes, topics, types, writer QoS, counts, bag rates |
|
|
62
|
+
| `diagnose` | Is anything wrong: one bounded sweep (nodes, topics, QoS pairs, restarts, TF, `/rosout` errors, a parameter read per node) ranked `critical` / `warning` / `info` with evidence; a check that cannot run is listed, never silent. Holds the ROS lock up to `budget_s` |
|
|
62
63
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
63
64
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
64
65
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -91,9 +92,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
91
92
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
92
93
|
```
|
|
93
94
|
|
|
94
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone
|
|
95
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone` and `auto` (`cyclone` if the binding imports, else `mock`). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the nine `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `diagnose`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` goes to the ROS half but runs no `ros2` process.
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
Fast DDS participants (ROS 2 nodes on `rmw_fastrtps_cpp`) need no backend of their own: the Cyclone backend observes them through standard RTPS discovery. Since 0.9.0 `TOPICFORGE_DDS_BACKEND=fast` is refused, like `rti`, `opensplice`, `coredx`, `intercom`, `opendds` and `dust`: startup stops with an error that lists the valid values (see [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md)). Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md).
|
|
97
98
|
|
|
98
99
|
## Configuration reference
|
|
99
100
|
|
|
@@ -103,7 +104,7 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
103
104
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
104
105
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
|
|
105
106
|
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
|
|
106
|
-
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `
|
|
107
|
+
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `auto`; anything else is rejected |
|
|
107
108
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
108
109
|
| `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 |
|
|
109
110
|
|
|
@@ -115,19 +116,18 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
115
116
|
|
|
116
117
|
External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
117
118
|
|
|
118
|
-
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`).
|
|
119
|
+
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). 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.
|
|
119
120
|
- 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.
|
|
120
121
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
121
122
|
- 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`.
|
|
122
123
|
- Single domain: the server observes the domain it joined at startup; changing it needs a restart.
|
|
123
124
|
- 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.
|
|
124
|
-
- Fast DDS serves no `list_endpoints`.
|
|
125
125
|
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
126
126
|
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
127
127
|
- Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
|
|
128
128
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
Not done yet: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (RTI, OpenDDS) and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
|
|
131
131
|
|
|
132
132
|
## Telemetry
|
|
133
133
|
|
|
@@ -143,10 +143,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
143
143
|
|
|
144
144
|
| Field | Example | Notes |
|
|
145
145
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
146
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
146
|
+
| `tool_name` | `"list_topics"` | One of the seventeen tools, never argument values |
|
|
147
147
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
148
148
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
149
|
-
| `version` | `"0.
|
|
149
|
+
| `version` | `"0.9.0"` | TopicForge server version |
|
|
150
150
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
151
151
|
| `success` | `true` | Whether the handler returned or raised |
|
|
152
152
|
|
|
@@ -174,7 +174,7 @@ python -m ruff check src tests
|
|
|
174
174
|
python -m pytest
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds`
|
|
177
|
+
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds` binding skip themselves when it is absent, and the `integration` marker (real-bus tests) is deselected by default. The `Makefile` (`make check`) uses POSIX shell syntax; on plain PowerShell run the commands above. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
178
178
|
|
|
179
179
|
## Upgrading
|
|
180
180
|
|
|
@@ -187,13 +187,13 @@ src/topicforge/
|
|
|
187
187
|
server/ MCP bootstrap, build_app(settings), prompts and instructions
|
|
188
188
|
tools/ thin MCPServer handlers, no backend logic
|
|
189
189
|
services/ input validation, orchestration, adapter factory
|
|
190
|
-
adapters/ ros2_live, ros2_mock, dds_cyclone,
|
|
190
|
+
adapters/ ros2_live, ros2_mock, dds_cyclone, common/ (binding-free logic)
|
|
191
191
|
models/ frozen Pydantic schemas, the contract with MCP clients
|
|
192
192
|
config/ settings and mode resolution
|
|
193
193
|
telemetry/ opt-in, off by default
|
|
194
194
|
examples/ mock walkthroughs (*.md) and runnable live DDS examples (dds/)
|
|
195
|
-
scripts/ real-bus interop checks
|
|
196
|
-
docs/ guides
|
|
195
|
+
scripts/ real-bus interop checks, ground-truth comparator, agent evaluation
|
|
196
|
+
docs/ guides and reference
|
|
197
197
|
tests/ pytest suite, mock-only, no ROS2 or DDS required
|
|
198
198
|
```
|
|
199
199
|
|
|
@@ -6,7 +6,7 @@ TopicForge (ROS 2 / DDS) is a local, read-only MCP server that speaks stdio.
|
|
|
6
6
|
Every client below launches it as a child process with the same command:
|
|
7
7
|
|
|
8
8
|
```
|
|
9
|
-
uvx --from "topicforge==0.
|
|
9
|
+
uvx --from "topicforge==0.9.0" topicforge
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
and the same three environment variables:
|
|
@@ -21,7 +21,7 @@ The registry name is `io.github.yaniswav/topicforge`. Where a client asks for a
|
|
|
21
21
|
display name, use **TopicForge (ROS 2 / DDS)**: an unrelated SEO product is also
|
|
22
22
|
called TopicForge in the MCP registries.
|
|
23
23
|
|
|
24
|
-
The version pin (`==0.
|
|
24
|
+
The version pin (`==0.9.0`) is moved on every release together with the other
|
|
25
25
|
version strings; a test keeps them in sync.
|
|
26
26
|
|
|
27
27
|
## Before you start
|
|
@@ -44,7 +44,7 @@ version strings; a test keeps them in sync.
|
|
|
44
44
|
`"--from"` in the args. To build the binding yourself, install the Cyclone C library and
|
|
45
45
|
run `pip install "topicforge[dds]"`. The `[dds]` and `[dds-cyclone]` extras still work
|
|
46
46
|
as aliases, so older commands keep installing.
|
|
47
|
-
- **pip alternative.** `pip install "topicforge==0.
|
|
47
|
+
- **pip alternative.** `pip install "topicforge==0.9.0"`, then use
|
|
48
48
|
`"command": "topicforge"` (or `"command": "python", "args": ["-m", "topicforge"]`)
|
|
49
49
|
with no `args` for uvx. Use the absolute path of the binary if the client does
|
|
50
50
|
not see your venv.
|
|
@@ -72,7 +72,7 @@ say where the file lives and what differs.
|
|
|
72
72
|
"mcpServers": {
|
|
73
73
|
"topicforge": {
|
|
74
74
|
"command": "uvx",
|
|
75
|
-
"args": ["--from", "topicforge==0.
|
|
75
|
+
"args": ["--from", "topicforge==0.9.0", "topicforge"],
|
|
76
76
|
"env": {
|
|
77
77
|
"TOPICFORGE_MODE": "auto",
|
|
78
78
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -95,7 +95,7 @@ claude plugin install topicforge@topicforge
|
|
|
95
95
|
Or just the server:
|
|
96
96
|
|
|
97
97
|
```
|
|
98
|
-
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.
|
|
98
|
+
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.9.0" topicforge
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## Claude Desktop
|
|
@@ -112,9 +112,9 @@ the common JSON shape above and restart the app completely. On Windows, if
|
|
|
112
112
|
|
|
113
113
|
The one-click links in this page and in the README are https redirects, because GitHub and PyPI strip `cursor://` and `vscode:` links. Only the raw schemes are documented by Cursor and VS Code; the https forms (`cursor.com/install-mcp`, `vscode.dev/redirect/mcp/install`) were observed to redirect correctly on 2026-10-06 but are not in the vendors' docs (unverified, last checked 2026-10-06). The raw links are given as a fallback.
|
|
114
114
|
|
|
115
|
-
One click: [Add TopicForge to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
115
|
+
One click: [Add TopicForge to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D)
|
|
116
116
|
|
|
117
|
-
If that page does not open Cursor, use the raw deeplink: `cursor://anysphere.cursor-deeplink/mcp/install?name=topicforge&config=
|
|
117
|
+
If that page does not open Cursor, use the raw deeplink: `cursor://anysphere.cursor-deeplink/mcp/install?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D`
|
|
118
118
|
|
|
119
119
|
Or paste the common JSON shape into `~/.cursor/mcp.json` (all projects) or
|
|
120
120
|
`.cursor/mcp.json` (one project).
|
|
@@ -125,9 +125,9 @@ The `plugin/` folder of this repository is also an [Agent Plugins](https://agent
|
|
|
125
125
|
|
|
126
126
|
## VS Code / GitHub Copilot
|
|
127
127
|
|
|
128
|
-
One click: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.
|
|
128
|
+
One click: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.9.0%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D)
|
|
129
129
|
|
|
130
|
-
Fallback, the raw URL handler: `vscode:mcp/install?%7B%22name%22%3A%22topicforge%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.
|
|
130
|
+
Fallback, the raw URL handler: `vscode:mcp/install?%7B%22name%22%3A%22topicforge%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.9.0%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D`
|
|
131
131
|
|
|
132
132
|
Or put this in `.vscode/mcp.json` (the key is `servers`, not `mcpServers`), or run
|
|
133
133
|
"MCP: Add Server" from the Command Palette:
|
|
@@ -138,7 +138,7 @@ Or put this in `.vscode/mcp.json` (the key is `servers`, not `mcpServers`), or r
|
|
|
138
138
|
"topicforge": {
|
|
139
139
|
"type": "stdio",
|
|
140
140
|
"command": "uvx",
|
|
141
|
-
"args": ["--from", "topicforge==0.
|
|
141
|
+
"args": ["--from", "topicforge==0.9.0", "topicforge"],
|
|
142
142
|
"env": {
|
|
143
143
|
"TOPICFORGE_MODE": "auto",
|
|
144
144
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -167,7 +167,7 @@ Servers", and paste the common JSON shape (the file is
|
|
|
167
167
|
Project file `.roo/mcp.json` (takes precedence) or the global `mcp_settings.json`
|
|
168
168
|
opened from the MCP view. Paste the common JSON shape. Roo's docs say Windows
|
|
169
169
|
needs a `cmd /c` wrapper for `npx`; for `uvx`, try it directly first and fall back
|
|
170
|
-
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.
|
|
170
|
+
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.9.0",
|
|
171
171
|
"topicforge"]` if it fails to start.
|
|
172
172
|
|
|
173
173
|
## Continue
|
|
@@ -184,7 +184,7 @@ mcpServers:
|
|
|
184
184
|
command: uvx
|
|
185
185
|
args:
|
|
186
186
|
- "--from"
|
|
187
|
-
- "topicforge==0.
|
|
187
|
+
- "topicforge==0.9.0"
|
|
188
188
|
- "topicforge"
|
|
189
189
|
env:
|
|
190
190
|
TOPICFORGE_MODE: auto
|
|
@@ -201,7 +201,7 @@ In `settings.json`, under `context_servers`:
|
|
|
201
201
|
"context_servers": {
|
|
202
202
|
"topicforge": {
|
|
203
203
|
"command": "uvx",
|
|
204
|
-
"args": ["--from", "topicforge==0.
|
|
204
|
+
"args": ["--from", "topicforge==0.9.0", "topicforge"],
|
|
205
205
|
"env": {
|
|
206
206
|
"TOPICFORGE_MODE": "auto",
|
|
207
207
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -227,7 +227,7 @@ run with the dds extra can exceed, so set it to 60 s:
|
|
|
227
227
|
```toml
|
|
228
228
|
[mcp_servers.topicforge]
|
|
229
229
|
command = "uvx"
|
|
230
|
-
args = ["--from", "topicforge==0.
|
|
230
|
+
args = ["--from", "topicforge==0.9.0", "topicforge"]
|
|
231
231
|
startup_timeout_sec = 60
|
|
232
232
|
|
|
233
233
|
[mcp_servers.topicforge.env]
|
|
@@ -258,7 +258,7 @@ extensions:
|
|
|
258
258
|
name: TopicForge (ROS 2 / DDS)
|
|
259
259
|
type: stdio
|
|
260
260
|
cmd: uvx
|
|
261
|
-
args: [--from, "topicforge==0.
|
|
261
|
+
args: [--from, "topicforge==0.9.0", topicforge]
|
|
262
262
|
enabled: true
|
|
263
263
|
envs: { "TOPICFORGE_MODE": "auto", "TOPICFORGE_DDS_BACKEND": "cyclone", "TOPICFORGE_DDS_DOMAIN_ID": "0" }
|
|
264
264
|
timeout: 300
|
|
@@ -269,7 +269,7 @@ extensions:
|
|
|
269
269
|
Program tab > Install > Edit `mcp.json` (same notation as Cursor), then paste the
|
|
270
270
|
common JSON shape. LM Studio's docs only show remote examples; local command
|
|
271
271
|
servers follow the Cursor notation but are not documented explicitly. Small
|
|
272
|
-
local models may struggle with
|
|
272
|
+
local models may struggle with seventeen tools.
|
|
273
273
|
|
|
274
274
|
## Amazon Q Developer / Kiro
|
|
275
275
|
|
|
@@ -367,7 +367,7 @@ when the question matches.
|
|
|
367
367
|
|
|
368
368
|
## Troubleshooting
|
|
369
369
|
|
|
370
|
-
- Run the command by hand first: `uvx --from "topicforge==0.
|
|
370
|
+
- Run the command by hand first: `uvx --from "topicforge==0.9.0" topicforge --version`.
|
|
371
371
|
- Server starts but sees no DDS participants: check the domain id, and that the
|
|
372
372
|
client machine is on the same network as the robot (DDS discovery uses
|
|
373
373
|
multicast UDP). Ask the assistant to call `health_check`.
|
|
@@ -523,7 +523,7 @@ moves to the envelope of the listing.
|
|
|
523
523
|
### 7.3 Dead fields removed from `TopicInfo`
|
|
524
524
|
|
|
525
525
|
`reader_count`, `writer_count` and `qos_profile` are removed. Checked by grep over `src/`:
|
|
526
|
-
no adapter (`ros2_live`, `ros2_mock`, `dds_cyclone`, `dds_fast
|
|
526
|
+
no adapter (`ros2_live`, `ros2_mock`, `dds_cyclone`, `dds_fast` (removed in 0.9.0), `composite`) and no
|
|
527
527
|
service ever assigns them, so they have always been `null`. Not to be confused with
|
|
528
528
|
`TopicSummary.writer_count` and `TopicSummary.reader_count` in `list_endpoints`, which are
|
|
529
529
|
filled and stay.
|
|
@@ -579,7 +579,7 @@ payloads are `_decode_status`, `_decode_note`, `_truncated_fields`, `_raw_text`,
|
|
|
579
579
|
overloaded: this is additive.
|
|
580
580
|
- `MessageSample.stamp_source` becomes non-nullable, with five values (2.2). It is set
|
|
581
581
|
explicitly in `adapters/dds_cyclone/adapter.py` (lines 165 and 442),
|
|
582
|
-
`adapters/dds_fast/adapter.py` (line 314), `adapters/common/dds_helpers.py`
|
|
582
|
+
`adapters/dds_fast/adapter.py` (line 314; adapter removed in 0.9.0), `adapters/common/dds_helpers.py`
|
|
583
583
|
(line 135) and the DDS fixtures of the mock adapter.
|
|
584
584
|
|
|
585
585
|
### 7.7 Decided by audit
|
|
@@ -1286,3 +1286,104 @@ Additive. `tools/list` grows by 467 bytes, to 97,315 (measured like `test_tool_l
|
|
|
1286
1286
|
`/robot_description` (section 9.15).
|
|
1287
1287
|
- HTTP transport: `TOPICFORGE_HTTP_TOKEN` and `--host` (SECURITY.md, docs/CLIENTS.md). No output
|
|
1288
1288
|
change.
|
|
1289
|
+
|
|
1290
|
+
### 9.19 Node names on QoS pairs (0.9.0, fix)
|
|
1291
|
+
|
|
1292
|
+
No shape change. On a live DDS backend, `detect_qos_mismatches` fills `reader_node` and
|
|
1293
|
+
`writer_node` from the same gid -> node map as `list_endpoints` (section 9.16). In 0.8.1 the QoS
|
|
1294
|
+
scan built its endpoints without that map, so both fields were `null` on every pair even when
|
|
1295
|
+
`list_endpoints` named the nodes. The mock already filled them, which is why the tests did not
|
|
1296
|
+
see it. Comparator: section `visibility`, row "QoS pairs name the writer node" on
|
|
1297
|
+
`06_detect_qos_mismatches`.
|
|
1298
|
+
|
|
1299
|
+
### 9.20 `tools/list` size: lane notes, field descriptions, ceiling (0.9.0)
|
|
1300
|
+
|
|
1301
|
+
Descriptions only, no shape change. `tools/list` goes from 97,315 to 95,391 bytes before
|
|
1302
|
+
`diagnose` (measured like `test_tool_listing_stays_small`).
|
|
1303
|
+
|
|
1304
|
+
- **Lane notes.** The `**Duration**` sentence of section 9.13 is shorter:
|
|
1305
|
+
``**Duration**: ROS lock, one call at a time, ends within 45 s; `busy` if held.`` (DDS lock for
|
|
1306
|
+
the DDS tools). Same facts; `test_tool_descriptions.py` still checks the lock, `45 s` and `busy`.
|
|
1307
|
+
- **Read-only phrases.** "Read-only; no side effects." and its variants are gone from the ROS 2
|
|
1308
|
+
tool descriptions: every tool declares `readOnlyHint` (section 2). "**Read-only by
|
|
1309
|
+
architecture**" stays on the DDS tools, which join the bus.
|
|
1310
|
+
- **Output field descriptions** are at most 120 characters, rewritten by hand with the same facts
|
|
1311
|
+
(`test_output_field_descriptions_stay_short`). Two lost a pointer only: `vendor` no longer names
|
|
1312
|
+
`vendor_source`, and `seen_count` no longer names `restarts_by_name`.
|
|
1313
|
+
- **Ceiling.** The test ceiling moves from 100,000 to 105,000 bytes, and the comparator's
|
|
1314
|
+
`tools/list bytes` row with it. No known client breaks in that range; the clients that truncate
|
|
1315
|
+
do it near 40,000 characters, a line crossed since 0.7.0.
|
|
1316
|
+
|
|
1317
|
+
### 9.21 diagnose (tool 17, 0.9.0)
|
|
1318
|
+
|
|
1319
|
+
The seventeenth tool served (the slot numbered 18 in the plan; 16 stays reserved for `get_image`).
|
|
1320
|
+
Additive: no existing tool changes shape. `tools/list` goes from 95,391 to 99,658 bytes (`diagnose`
|
|
1321
|
+
is 4,267: description 775, input schema 449, output schema 2,842), under the 105,000 test ceiling
|
|
1322
|
+
of section 9.20.
|
|
1323
|
+
|
|
1324
|
+
**Call.** `diagnose(scope="all", budget_s=30, node=null)`. `scope` is `all`, `graph` (nodes,
|
|
1325
|
+
topics, parameter reads), `tf`, `dds` or `logs`; `budget_s` is 10..40; `node` is read first. Only
|
|
1326
|
+
invalid input raises (an `AdapterError`, so `isError: true`); a failing check never does.
|
|
1327
|
+
|
|
1328
|
+
**Lane and budget.** ROS lane. The call holds the ROS lock for up to `budget_s`: other ROS tools
|
|
1329
|
+
answer `busy` meanwhile, `health_check` does not. The internal deadline is
|
|
1330
|
+
`min(now + budget_s, guard deadline - 2 s)`; a check that no longer fits is listed in
|
|
1331
|
+
`checks_skipped` with reason `budget`. The tf window shrinks to what is left (an `info` says so).
|
|
1332
|
+
|
|
1333
|
+
**Sources.** Existing services only, no adapter method added: `list_nodes`, `list_topics`,
|
|
1334
|
+
`get_node_info`, `inspect_tf` (`window_s` 8, `stale_after_s` 2), `sample_messages` (`/rosout`, 50,
|
|
1335
|
+
`min_log_level` `error`, `timeout_s` 5: only lines logged while it listens), `detect_qos_mismatches`, `participant_events` (300 s),
|
|
1336
|
+
`list_participants` and the health report. On the DDS side it never calls `peek_dds_samples` or
|
|
1337
|
+
`topic_metrics`: the three DDS calls it makes are answered by the discovery caches
|
|
1338
|
+
(`tests/test_diagnose.py` records the adapter methods called and fails on either).
|
|
1339
|
+
|
|
1340
|
+
**Checks, in execution order, and the severity table.** A `critical` exists only for a row below.
|
|
1341
|
+
|
|
1342
|
+
| Check | critical | warning | info |
|
|
1343
|
+
|---|---|---|---|
|
|
1344
|
+
| `environment` | - | - | mock mode; SROS2 enabled. A missing backend is a `checks_skipped` entry |
|
|
1345
|
+
| `nodes` | - | a duplicated name; no node on a live graph | lifecycle nodes (state not read) |
|
|
1346
|
+
| `topics` | - | - | subscribers without a publisher, one finding (normal for command inputs such as `/cmd_vel`); publishers without a subscriber, one finding (`/rosout` and `/parameter_events` left out) |
|
|
1347
|
+
| `dds` | an `incompatible` QoS pair; a pair that never matches (partition, type name); a node with 3 or more lives in the window | a `risky` pair; a near-homonym topic hint; a node with 2 lives | participants that left; no endpoint visible |
|
|
1348
|
+
| `tf` | - | `stale`, `two_parents`, `cycle`, `static_and_dynamic`; a URDF link with no frame | several roots; `urdf_note`; a window cut by the budget |
|
|
1349
|
+
| `logs` | - | one per node that logged an `error` or `fatal` line (the newest line quoted) | no such line |
|
|
1350
|
+
| `responsiveness` | the node named in `node` did not answer a parameter read in 6 s | another node did not answer in 3 s (the text says to confirm with `get_node_info(node, timeout_s=8)`) | nodes the budget did not reach, named |
|
|
1351
|
+
|
|
1352
|
+
`responsiveness` runs last, as the budget sink: nodes in the order named, lifecycle, then by name
|
|
1353
|
+
(`_ros2cli_*` left out), 3 s each while the budget lasts. A writer without a reader is never above
|
|
1354
|
+
`info`, and `ros_discovery_info` pairs stay out of the QoS scan.
|
|
1355
|
+
|
|
1356
|
+
**Skips.** An `AdapterError` inside a check, or a backend the health report says is absent (no
|
|
1357
|
+
`ros2` CLI for the ROS checks, no DDS backend for `dds`), becomes a `checks_skipped` entry
|
|
1358
|
+
`{check, reason}` carrying the message. A node whose read raises (the named node is not on the
|
|
1359
|
+
graph) is listed the same way, with the node in `reason`, and the other nodes are still read.
|
|
1360
|
+
|
|
1361
|
+
**Output.** `Diagnosis`: `findings` sorted by severity (`critical`, `warning`, `info`), then
|
|
1362
|
+
`check`, then `subject`, capped at 50 with `findings_total`; `critical_count`, `warning_count` and
|
|
1363
|
+
`info_count` cover all findings; `checks_run`, `checks_skipped`, `budget_s`, `elapsed_s`,
|
|
1364
|
+
`verdict`, `mode_effective`, `note`. A `Finding` has `severity`, `check`, `subject` (topic, node or
|
|
1365
|
+
frame), `text`, `evidence` (`{tool, field, value}`, `value` cut at 200 characters) and
|
|
1366
|
+
`suggested_fixes` (the `SuggestedFix` of section 3.1, copied from `detect_qos_mismatches`, empty
|
|
1367
|
+
elsewhere; the forbidden-verb test covers the copy).
|
|
1368
|
+
|
|
1369
|
+
**Never healthy with a skip.** The verdict is one line and never says an all-clear while a check
|
|
1370
|
+
was skipped or the scope left checks out: it starts `Incomplete sweep:` and names what did not
|
|
1371
|
+
run. Only a full sweep with no skip and no critical or warning finding says `no critical or
|
|
1372
|
+
warning finding in the N checks run; data flow itself is not observed`. The word "healthy" is
|
|
1373
|
+
not used: discovery and `/rosout` show declared state, not data flow. On a live ROS 2-only setup
|
|
1374
|
+
(no DDS backend) the `dds` check is always skipped, so the verdict is never the clean line.
|
|
1375
|
+
|
|
1376
|
+
**Mock.** The fixtures give one `critical` (the RELIABLE reader against the BEST_EFFORT writer on
|
|
1377
|
+
`/dds/qos_mismatch`, with its fixes), warnings for the stale `map -> odom`, the URDF link
|
|
1378
|
+
`gripper_link` and one per node with an error on `/rosout`, and three `info`; `checks_skipped` is
|
|
1379
|
+
empty and `elapsed_s` is under 1 s (`test_the_mock_diagnosis_is_derived_from_the_fixtures`).
|
|
1380
|
+
|
|
1381
|
+
**Bench.** `calls_diagnose.json` (between `calls_nav_primitives.json` and `calls_graph_diff.json`,
|
|
1382
|
+
bench only): `50_diagnose` (`budget_s` 40) and `51_diagnose_node` (`node` `/bench_blocked`). The
|
|
1383
|
+
truth is the exact set of critical and warning findings the bench seeds (`stale` `map -> odom`,
|
|
1384
|
+
`tool` with two parents, `arm_link`, the errors of `/bench_robot`, the blocked node: a `warning`
|
|
1385
|
+
in `50`, or an `info` that names it if the budget did not reach it; a `critical` in `51`). Any
|
|
1386
|
+
other critical or warning is a false alarm and fails the cell. Also required: `checks_skipped`
|
|
1387
|
+
empty, `elapsed_s` within `budget_s`, counts that match the findings, a verdict that is not the
|
|
1388
|
+
clean line. Comparator section: `diagnose`.
|
|
1389
|
+
|