topicforge 0.5.3__tar.gz → 0.5.5__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.3 → topicforge-0.5.5}/.gitignore +5 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/CHANGELOG.md +233 -3
- topicforge-0.5.5/PKG-INFO +245 -0
- topicforge-0.5.5/README.md +186 -0
- topicforge-0.5.5/docs/DDS_QUICKSTART.md +126 -0
- topicforge-0.5.5/docs/TESTING.md +105 -0
- topicforge-0.5.5/docs/TROUBLESHOOTING.md +77 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/docs/TUTORIEL.md +3 -71
- {topicforge-0.5.3 → topicforge-0.5.5}/docs/dds-interop-matrix.md +9 -5
- topicforge-0.5.5/docs/pro.md +38 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/docs/product-plan.md +11 -31
- topicforge-0.5.5/examples/README.md +22 -0
- topicforge-0.5.5/examples/dds/00_hello_pub_sub/README.md +113 -0
- topicforge-0.5.5/examples/dds/01_who_is_on_the_bus/README.md +58 -0
- topicforge-0.5.5/examples/dds/02_why_cant_they_talk/README.md +64 -0
- topicforge-0.5.5/examples/dds/03_a_node_crashed/README.md +62 -0
- topicforge-0.5.5/examples/dds/04_late_joiner_misses_data/README.md +53 -0
- topicforge-0.5.5/examples/dds/05_reliability_in_code/README.md +102 -0
- topicforge-0.5.5/examples/dds/06_durability_late_joiner_in_code/README.md +127 -0
- topicforge-0.5.5/examples/dds/07_deadline_in_code/README.md +112 -0
- topicforge-0.5.5/examples/dds/08_crash_seen_from_inside/README.md +120 -0
- topicforge-0.5.5/examples/dds/10_lidar_silent_after_driver_swap/README.md +67 -0
- topicforge-0.5.5/examples/dds/11_who_talks_to_whom/README.md +53 -0
- topicforge-0.5.5/examples/dds/12_safety_monitor_dropout/README.md +70 -0
- topicforge-0.5.5/examples/dds/13_deadline_not_offered/README.md +66 -0
- topicforge-0.5.5/examples/dds/14_restart_loop/README.md +52 -0
- topicforge-0.5.5/examples/dds/README.md +145 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/pyproject.toml +7 -1
- topicforge-0.5.5/scripts/agent_eval/README.md +37 -0
- topicforge-0.5.5/scripts/integration/README.md +186 -0
- topicforge-0.5.5/scripts/integration/publishers/cyclone_c/README.md +41 -0
- topicforge-0.5.5/scripts/integration/publishers/cyclone_cpp/README.md +44 -0
- topicforge-0.5.5/scripts/integration/publishers/cyclone_rust/README.md +48 -0
- topicforge-0.5.5/scripts/integration/publishers/dust_py/README.md +35 -0
- topicforge-0.5.5/scripts/integration/publishers/fast_publisher_cpp/README.md +32 -0
- topicforge-0.5.5/scripts/integration/publishers/fast_py/README.md +65 -0
- topicforge-0.5.5/scripts/integration/publishers/opensplice_publisher/README.md +123 -0
- topicforge-0.5.5/scripts/integration/publishers/rti_c/README.md +62 -0
- topicforge-0.5.5/scripts/integration/publishers/rti_cpp/README.md +65 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/__init__.py +1 -1
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/base.py +11 -2
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/__init__.py +73 -1
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/dds_helpers.py +90 -27
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/dds_introspection.py +122 -10
- topicforge-0.5.5/src/topicforge/adapters/common/discovery_tracker.py +497 -0
- topicforge-0.5.5/src/topicforge/adapters/common/endpoints.py +394 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/lifecycle.py +56 -7
- topicforge-0.5.5/src/topicforge/adapters/common/qos_analyzer.py +304 -0
- topicforge-0.5.5/src/topicforge/adapters/common/qos_endpoints.py +73 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/qos_normalize.py +125 -8
- topicforge-0.5.5/src/topicforge/adapters/common/qos_scan.py +385 -0
- topicforge-0.5.5/src/topicforge/adapters/common/topic_filter.py +106 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/composite.py +24 -2
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_cyclone/adapter.py +219 -165
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_dust/adapter.py +12 -2
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_fast/adapter.py +22 -20
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_opendds/adapter.py +12 -2
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/ros2_live/adapter.py +12 -2
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/ros2_mock/adapter.py +13 -3
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/ros2_mock/fixtures.py +154 -21
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/models/__init__.py +16 -0
- topicforge-0.5.5/src/topicforge/models/schemas.py +1281 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/services/health.py +33 -1
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/services/inspector.py +31 -2
- topicforge-0.5.5/src/topicforge/tools/handlers.py +601 -0
- topicforge-0.5.5/tests/integration/__init__.py +1 -0
- topicforge-0.5.5/tests/integration/test_real_bus.py +56 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_composite_adapter.py +5 -4
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_cyclone_adapter.py +78 -6
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_dds_cross_vendor.py +16 -10
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_dds_helpers.py +60 -34
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_dds_introspection.py +120 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_dds_qos_normalization.py +84 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_dds_schemas.py +1 -1
- topicforge-0.5.5/tests/test_discovery_tracker.py +462 -0
- topicforge-0.5.5/tests/test_endpoints.py +444 -0
- topicforge-0.5.5/tests/test_example_node_spec.py +190 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_factory.py +4 -3
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_fast_adapter.py +4 -1
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_health.py +22 -0
- topicforge-0.5.5/tests/test_honest_outputs.py +250 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_inspector.py +2 -2
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_mock_adapter.py +25 -2
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_qos_analyzer.py +205 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_qos_endpoints.py +18 -13
- topicforge-0.5.5/tests/test_qos_scan.py +388 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_tools_integration.py +9 -0
- topicforge-0.5.3/PKG-INFO +0 -440
- topicforge-0.5.3/README.md +0 -381
- topicforge-0.5.3/docs/DDS_QUICKSTART.md +0 -206
- topicforge-0.5.3/docs/MIGRATION_v0.1_to_v0.2.md +0 -140
- topicforge-0.5.3/docs/MIGRATION_v0.2_to_v0.3.md +0 -157
- topicforge-0.5.3/docs/MIGRATION_v0.3_to_v0.4.md +0 -248
- topicforge-0.5.3/docs/TESTING.md +0 -434
- topicforge-0.5.3/docs/TROUBLESHOOTING.md +0 -312
- topicforge-0.5.3/docs/pro.md +0 -65
- topicforge-0.5.3/examples/README.md +0 -37
- topicforge-0.5.3/scripts/integration/README.md +0 -124
- topicforge-0.5.3/src/topicforge/adapters/common/qos_analyzer.py +0 -98
- topicforge-0.5.3/src/topicforge/adapters/common/qos_endpoints.py +0 -95
- topicforge-0.5.3/src/topicforge/models/schemas.py +0 -666
- topicforge-0.5.3/src/topicforge/tools/handlers.py +0 -452
- topicforge-0.5.3/tests/integration/__init__.py +0 -16
- topicforge-0.5.3/tests/integration/conftest.py +0 -58
- topicforge-0.5.3/tests/integration/scenarios/lifecycle_tracking.json +0 -34
- topicforge-0.5.3/tests/integration/scenarios/multi_vendor_basic.json +0 -39
- topicforge-0.5.3/tests/integration/scenarios/qos_mismatch_detection.json +0 -47
- topicforge-0.5.3/tests/integration/scenarios/topic_metrics_frequency.json +0 -30
- topicforge-0.5.3/tests/integration/scenarios/topic_metrics_sequence_gaps.json +0 -34
- topicforge-0.5.3/tests/integration/scenarios/xtypes_decode.json +0 -29
- topicforge-0.5.3/tests/integration/test_real_bus.py +0 -89
- topicforge-0.5.3/tests/integration/test_scenarios_schema.py +0 -140
- {topicforge-0.5.3 → topicforge-0.5.5}/LICENSE +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/__main__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/common/xtypes.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_dust/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/dds_opendds/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/config/settings.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/constants.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/server/app.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/services/bag_service.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/services/factory.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/__init__.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/conftest.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/fixtures/csv_echo_imu.txt +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/fixtures/csv_echo_pose_multi.txt +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_analyze_bag_multi_format.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_bag_service.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_cdr_decoder.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_config.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_dust_adapter.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_lifecycle_buffer.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_metrics_buffer.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_opendds_adapter.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_telemetry.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_topic_metrics.py +0 -0
- {topicforge-0.5.3 → topicforge-0.5.5}/tests/test_xtypes.py +0 -0
|
@@ -230,3 +230,8 @@ CLAUDE*.md
|
|
|
230
230
|
# Pro tier: paid features kept out of the open-source repo
|
|
231
231
|
# -------------------------------------------------------------------------
|
|
232
232
|
pro/
|
|
233
|
+
|
|
234
|
+
# Vendor license files for the demo participants (never commit)
|
|
235
|
+
scripts/integration/**/rti_license.dat
|
|
236
|
+
scripts/integration/**/*.dat
|
|
237
|
+
.venv-demo/
|
|
@@ -7,6 +7,233 @@ and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.5] - 2026-10-02
|
|
11
|
+
|
|
12
|
+
Driven by a blind evaluation: agents given only TopicForge's tools had to
|
|
13
|
+
diagnose live DDS buses with planted faults. The first rounds (on 0.5.4) found
|
|
14
|
+
wrong blame on QoS, missed Liveliness and Ownership faults, hand-joined GUIDs
|
|
15
|
+
and empty results read as "healthy". After the changes below, 16 of 16
|
|
16
|
+
scenarios were diagnosed correctly, with no false alarm on a healthy bus.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`list_endpoints`, the 12th MCP tool.** Returns every announced DDS writer
|
|
21
|
+
and reader as a typed `EndpointInfo` (role, topic, type, owning participant
|
|
22
|
+
guid, name and vendor, structured QoS, announcement timestamp) plus a
|
|
23
|
+
`by_topic` roll-up that flags orphans (`no_reader`, `no_writer`). TopicForge's
|
|
24
|
+
own endpoints are excluded unless `include_observer`, and counted in
|
|
25
|
+
`excluded_observer_endpoints`. Endpoints of a participant that left are kept
|
|
26
|
+
(200 entries, 1 hour) and shown as `departed_writers` / `departed_readers`;
|
|
27
|
+
`include_departed` lists them. Served by Cyclone and the mock. Fast raises a
|
|
28
|
+
clear "not supported yet" error.
|
|
29
|
+
- **Continuous discovery tracking on Cyclone.** A daemon thread (0.5 s period)
|
|
30
|
+
is the only code that reads the builtin discovery topics and feeds in-memory
|
|
31
|
+
caches that every discovery tool reads. Lifecycle no longer moves only when a
|
|
32
|
+
tool is called: a node restarted three times shows as 3 `lost` and 4
|
|
33
|
+
`discovered`, dated by DDS. The 2 s warm-up sleep is gone.
|
|
34
|
+
- Timestamps on `participant_events` and `list_participants`: `announced_ns`,
|
|
35
|
+
`lost_ns`, `lost_time_source`, and on events `time_source` and `observed_ns`.
|
|
36
|
+
A DDS source timestamp and a local observation are never mixed.
|
|
37
|
+
- `health_check` gains `now_ns`, `observer_started_ns`, `observed_domain_note`
|
|
38
|
+
and the tracker status (`tracker_running`, `tracker_passes`, `tracker_errors`,
|
|
39
|
+
`tracker_last_pass_ns`).
|
|
40
|
+
- `QosProfile` gains optional `liveliness_kind`, `liveliness_lease_ns`,
|
|
41
|
+
`ownership_kind`, `ownership_strength`, `partitions`, `latency_budget_ns`,
|
|
42
|
+
`destination_order` and `data_representation`, read from Cyclone discovery.
|
|
43
|
+
A missing Partition policy is reported as `[""]` everywhere.
|
|
44
|
+
- `peek_dds_samples` on `DCPSPublication`, `DCPSSubscription` and
|
|
45
|
+
`DCPSParticipant` carries structured `role`, `participant_guid`,
|
|
46
|
+
`participant_name`, `type_id`, `qos`, `announced_ns` and `is_observer`, and
|
|
47
|
+
sets `timestamp_ns` from the announcement. `_raw_text` is kept only for a
|
|
48
|
+
sample with nothing structured to read, truncated to 300 characters.
|
|
49
|
+
- `peek_dds_samples` on a builtin topic returns the cached discovery state, not
|
|
50
|
+
a stream.
|
|
51
|
+
- Topic filters accept `rt/x` and `x` interchangeably and report which form
|
|
52
|
+
matched. A filter that matches nothing returns the list of known topics.
|
|
53
|
+
- Examples: the generic role nodes take partition, liveliness and ownership
|
|
54
|
+
options.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- **Breaking: `detect_qos_mismatches` returns a `MismatchScan` envelope instead
|
|
59
|
+
of a bare list.** To migrate, read `["reports"]` where you used the list. The
|
|
60
|
+
envelope also carries `matched`, `not_matched`, `hints`, `pairs_checked`,
|
|
61
|
+
`topics_scanned`, `policies_checked`, `policies_unchecked` and
|
|
62
|
+
`mode_effective`. Why: an empty list was read as a healthy bus although only
|
|
63
|
+
four policies were checked, and readers and writers in different partitions
|
|
64
|
+
were blamed on Reliability.
|
|
65
|
+
- Partition is checked first (`*` and `?` wildcards; a wildcard against a
|
|
66
|
+
wildcard never matches). A pair it separates is a `not_matched` entry with
|
|
67
|
+
reason `partition`; differing type names give reason `type_name`. The RxO
|
|
68
|
+
rules are not run on either. A `not_matched` pair lists the policies that
|
|
69
|
+
would be incompatible if it matched (`latent_incompatible_policies`).
|
|
70
|
+
- Liveliness (kind and lease), LatencyBudget, Ownership (kind equality),
|
|
71
|
+
DestinationOrder and DataRepresentation join Reliability, Durability and
|
|
72
|
+
Deadline. History stays a labelled `risky` finding. A policy a side did not
|
|
73
|
+
announce is skipped, never guessed.
|
|
74
|
+
- `MismatchReport` gains, additively, the reader and writer participant guid and
|
|
75
|
+
name, type names, `details` (requested and offered value, failed rule) and
|
|
76
|
+
`unchecked`.
|
|
77
|
+
- Hints cover orphan topics whose names differ by at most 2 edits (compared
|
|
78
|
+
against all topics), path-suffix matches and XTypes type id differences (a
|
|
79
|
+
note, never a mismatch). Hints are prioritized and say how many were omitted.
|
|
80
|
+
- A late joiner is not a hint (it is normal on most buses): a `MatchedPair` is
|
|
81
|
+
flagged `late_joiner` when its writer is VOLATILE and the reader, on the same
|
|
82
|
+
host, appeared more than 1 s later.
|
|
83
|
+
- `reports`, `matched` and `not_matched` are capped at 200 entries each, with
|
|
84
|
+
`reports_total`, `matched_total`, `not_matched_total` and `truncated`.
|
|
85
|
+
- `topic_metrics` reports a `status`, and on a user topic says it has no data
|
|
86
|
+
instead of returning zeros. `peek_dds_samples` on a user topic returns count
|
|
87
|
+
0 and a note. `ParticipantInfo` and endpoints gain `is_observer`, and
|
|
88
|
+
`vendor_source` says where a vendor came from.
|
|
89
|
+
- Tool descriptions no longer carry internal history, and state the
|
|
90
|
+
single-domain and EXCLUSIVE-ownership facts.
|
|
91
|
+
- `EndpointInfo.activity` is reserved (always `None`) with an `activity_note`
|
|
92
|
+
saying liveness is not observed.
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- Infinite durations (cyclonedds reports 9223372036854775807) are normalized
|
|
97
|
+
to `None`; an infinite Deadline used to surface as a 9.2e18 ns deadline.
|
|
98
|
+
- The Cyclone discovery tracker is stopped at interpreter exit.
|
|
99
|
+
- A race between the tracker and a tool call could mark a live participant as
|
|
100
|
+
lost for good; a failed read could drop a participant's departure; endpoints
|
|
101
|
+
of a participant that had just left could stay listed as live. The tracker
|
|
102
|
+
and every tool now share one lock, and taken samples are never discarded.
|
|
103
|
+
- The cyclonedds Python binding is not thread-safe when it converts QoS: two
|
|
104
|
+
threads in `take()` at once corrupted the heap on Windows. Every binding call
|
|
105
|
+
now goes through one process-wide lock, and tool handlers no longer call the
|
|
106
|
+
binding at all.
|
|
107
|
+
- If every tracker pass fails, tools no longer wait 3 s each: warm-up is bounded
|
|
108
|
+
once. `health_check` reports failed passes and cache evictions.
|
|
109
|
+
- An unreadable QoS duration is treated as unknown (`QosProfile.unknown_policies`),
|
|
110
|
+
not as infinite, so it cannot produce a false Deadline incompatibility.
|
|
111
|
+
- Topic-name typo hints are bounded (banded edit distance, orphan cap, call
|
|
112
|
+
budget), so a bus with a thousand topics does not stall the call.
|
|
113
|
+
- Partition matching was checked against a live Cyclone bus: `*` and `?` are
|
|
114
|
+
wildcards, `[...]` is literal, and two wildcard expressions never match each
|
|
115
|
+
other. Pinned by tests.
|
|
116
|
+
|
|
117
|
+
### Known limits
|
|
118
|
+
|
|
119
|
+
- A hung writer (alive, lease renewed, no data) is not observable without a
|
|
120
|
+
data probe. An opt-in probe is planned for 0.5.6.
|
|
121
|
+
- A crash and a clean leave cannot be told apart, and `lost_ns` is an upper
|
|
122
|
+
bound of the death (the lease expiry after a crash).
|
|
123
|
+
- A participant that cycles faster than the discovery history depth between two
|
|
124
|
+
tracker passes can be missed.
|
|
125
|
+
- The Fast DDS backend has never run on a bus, and `list_endpoints` is not
|
|
126
|
+
supported there.
|
|
127
|
+
- DDS Security is not supported.
|
|
128
|
+
- User-topic payload decoding is disabled, so `topic_metrics` has no data on
|
|
129
|
+
user topics.
|
|
130
|
+
|
|
131
|
+
## [0.5.4] - 2026-10-02
|
|
132
|
+
|
|
133
|
+
First run of the DDS code against a live multi-vendor bus (Windows 11, a
|
|
134
|
+
Python / Cyclone DDS participant and a Rust / Dust DDS participant, TopicForge
|
|
135
|
+
driven by a real MCP client). Until now every DDS adapter had only been
|
|
136
|
+
checked statically. The run exposed four defects that together made the DDS
|
|
137
|
+
module non-functional on Cyclone; all are fixed and pinned by tests.
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- Role nodes published below their rate (about 7 Hz for 10 Hz on Windows):
|
|
142
|
+
they now keep an absolute schedule.
|
|
143
|
+
|
|
144
|
+
- **`list_participants` now reports the participant name and the hostname on
|
|
145
|
+
Cyclone.** The name comes from the EntityName QoS and the hostname from the
|
|
146
|
+
`__Hostname` discovery property; both were always null on a real bus.
|
|
147
|
+
- **Participant GUIDs were never read.** cyclonedds 11.0.1 exposes the builtin
|
|
148
|
+
key as a `uuid.UUID`, which the extractor did not handle, so every
|
|
149
|
+
participant collapsed onto a single `unknown` entry.
|
|
150
|
+
- **Vendors were never identified.** The builtin participant sample carries no
|
|
151
|
+
vendor field. The vendor id is now read from the first two bytes of the GUID
|
|
152
|
+
prefix, as RTPS recommends; implementations that do not follow that
|
|
153
|
+
convention (Dust DDS, and RTI by default) still report `unknown`, because
|
|
154
|
+
the Cyclone Python binding does not expose the vendor id from the RTPS
|
|
155
|
+
header.
|
|
156
|
+
- **The OMG vendor-id table was wrong.** It mapped `01.05` to Fast DDS and
|
|
157
|
+
`01.16` to Cyclone; the correct ids are `01.0F` (eProsima) and `01.10`
|
|
158
|
+
(Eclipse), verified against both vendors' sources. The Cyclone side ran on a
|
|
159
|
+
live bus; the Fast DDS side (`fast_extract_vendor_id`) is verified
|
|
160
|
+
statically only, since its tests need a binding that is not on PyPI. The `vendor` field of
|
|
161
|
+
`ParticipantInfo` and `ParticipantEvent` now also accepts `rti_micro`,
|
|
162
|
+
`opensplice`, `opendds`, `coredx`, `intercom` and `dust` (soft-breaking for
|
|
163
|
+
clients validating the previous enum).
|
|
164
|
+
- **`detect_qos_mismatches` never reported anything on Cyclone.** cyclonedds
|
|
165
|
+
scopes its policy class names (`Reliability.BestEffort`), and the
|
|
166
|
+
normalizer matched only the bare name, so no QoS profile was ever built.
|
|
167
|
+
- **A stopped participant never disappeared.** Discovery readers keep the last
|
|
168
|
+
sample of a departed participant with a NOT_ALIVE instance state; those are
|
|
169
|
+
now ignored for participants and endpoints, so a participant is reported as
|
|
170
|
+
left when its lease expires, and a dead endpoint no longer produces a
|
|
171
|
+
mismatch.
|
|
172
|
+
|
|
173
|
+
- Cyclone adapter created a new DDS reader on a builtin discovery topic on
|
|
174
|
+
every tool call and never deleted it. It now keeps one reader per builtin
|
|
175
|
+
topic and takes a non-blocking snapshot, so calls no longer wait 2 s each
|
|
176
|
+
(`detect_qos_mismatches` waited 4 s).
|
|
177
|
+
- `peek_dds_samples` on `DCPSPublication` / `DCPSSubscription` reported the
|
|
178
|
+
endpoints of participants that had left; disposed entries are now dropped.
|
|
179
|
+
Its payload also carries the endpoint `type_name`.
|
|
180
|
+
- `test_dds_cross_vendor.py` expected an error message from v0.3; it had
|
|
181
|
+
never run, since CI has no DDS binding. First run against the real Cyclone
|
|
182
|
+
binding.
|
|
183
|
+
|
|
184
|
+
### Added
|
|
185
|
+
|
|
186
|
+
- `examples/dds/`: a "write the code" track. Examples 00 (hello publisher
|
|
187
|
+
and subscriber), 05 (Reliability), 06 (Durability and the late joiner),
|
|
188
|
+
07 (Deadline declared and kept) and 08 (a crash seen from inside and from
|
|
189
|
+
outside) each ship a readable `publisher.py` and `subscriber.py` in Cyclone
|
|
190
|
+
DDS Python; the subscriber prints what it receives and the DDS statuses,
|
|
191
|
+
and TopicForge explains the same situation from outside. All fourteen
|
|
192
|
+
examples pass on a live bus.
|
|
193
|
+
- The generic role nodes print one line per second per reader with what
|
|
194
|
+
they received, and examples 02, 04, 10 and 13 check that the broken
|
|
195
|
+
subscriber receives nothing while the control one receives data.
|
|
196
|
+
- The harness keeps each program's output in a log file and prints it live
|
|
197
|
+
under `--hold`.
|
|
198
|
+
|
|
199
|
+
- `scripts/integration/interop_check.py`: one-command multi-vendor demo
|
|
200
|
+
that starts a Rust / Dust and a Python / Cyclone participant, drives
|
|
201
|
+
TopicForge over stdio through the official MCP client, checks participant
|
|
202
|
+
discovery, a deliberate Reliability mismatch between the two vendors, and
|
|
203
|
+
the departure of a stopped participant, then stops every process it started.
|
|
204
|
+
- A real Rust / Dust DDS participant (`publishers/dust_publisher`) and a real
|
|
205
|
+
Python / Cyclone participant replacing the previous scaffold, which never
|
|
206
|
+
wrote a sample.
|
|
207
|
+
- Twelve interop programs in total, one per vendor and language with an
|
|
208
|
+
officially released binding (Cyclone C / C++ / Rust / Python, Dust Rust /
|
|
209
|
+
Python, Fast DDS C++ / Python, RTI Connext C / C++ / Python, OpenSplice C),
|
|
210
|
+
all following the contract in `scripts/integration/DEMO_CONTRACT.md`. The
|
|
211
|
+
driver starts whichever ones are built on the host and adapts its checks;
|
|
212
|
+
`--list` shows what can run. Only Cyclone Python and Dust Rust / Python / C
|
|
213
|
+
have been run, on Windows; the rest are written but unrun. RTI participants
|
|
214
|
+
need a local license and are never run in CI.
|
|
215
|
+
- One-command launch scripts, `scripts/integration/launch/setup` and
|
|
216
|
+
`run_demo` (`.ps1` and `.sh`), which create `.venv-demo`, install the Cyclone
|
|
217
|
+
binding and build the Rust participant. `setup.ps1 -Firewall` adds inbound
|
|
218
|
+
UDP 7400-7500 rules on private networks for multi-machine runs.
|
|
219
|
+
- Unicast peer configuration for Cyclone and Fast DDS and a guide for a mixed
|
|
220
|
+
Linux and Windows bus (`scripts/integration/config/`).
|
|
221
|
+
- `.github/workflows/demo.yml` runs the driver with the Cyclone and Dust
|
|
222
|
+
participants on Ubuntu and Windows; `demo-fast.yml` builds Fast DDS 3 from
|
|
223
|
+
pinned tags and starts the C++ participant (weekly and manual).
|
|
224
|
+
- `scripts/integration/README.md` rewritten around the demo: it previously
|
|
225
|
+
described the removed docker / scenario rig.
|
|
226
|
+
|
|
227
|
+
- `examples/dds/`: real use cases 10 to 14 (driver swap, wiring, safety
|
|
228
|
+
monitor dropout, deadline not offered, restart loop) next to the concept
|
|
229
|
+
examples 01 to 04. All nine pass on a live bus.
|
|
230
|
+
|
|
231
|
+
### Removed
|
|
232
|
+
|
|
233
|
+
- The docker / scenario integration rig (`scenarios_runner.py`, `run-local.*`,
|
|
234
|
+
`docker-compose.yml`, per-vendor Dockerfiles, scenario JSON files, schema
|
|
235
|
+
test and `integration.yml`) is removed in favour of the demo driver.
|
|
236
|
+
|
|
10
237
|
## [0.5.3] - 2026-10-01
|
|
11
238
|
|
|
12
239
|
Fixes from an independent senior review of the whole repository (five
|
|
@@ -23,8 +250,9 @@ the findings.
|
|
|
23
250
|
because the names are unclaimed anyone could have registered them: users
|
|
24
251
|
following the documented `pip install topicforge[dds]` would then have run
|
|
25
252
|
that code. Those extras are removed; `[dds]` now means Cyclone only. The
|
|
26
|
-
metadata of 0.3.0 to 0.5.2 is immutable on PyPI,
|
|
27
|
-
|
|
253
|
+
metadata of 0.3.0 to 0.5.2 is immutable on PyPI, so those releases have
|
|
254
|
+
been yanked: `pip install topicforge` no longer selects them, although an
|
|
255
|
+
exact pin such as `topicforge==0.5.2` still installs one.
|
|
28
256
|
- **Removed the automatic `topicforge_pro` plugin hook.** At startup the
|
|
29
257
|
server imported any installed package named `topicforge_pro` and handed it
|
|
30
258
|
the full MCP server instance, so a third-party package with that
|
|
@@ -1007,7 +1235,9 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1007
1235
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1008
1236
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1009
1237
|
|
|
1010
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.
|
|
1238
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.5...HEAD
|
|
1239
|
+
[0.5.5]: https://github.com/yaniswav/TopicForge/compare/v0.5.4...v0.5.5
|
|
1240
|
+
[0.5.4]: https://github.com/yaniswav/TopicForge/compare/v0.5.3...v0.5.4
|
|
1011
1241
|
[0.5.3]: https://github.com/yaniswav/TopicForge/compare/v0.5.2...v0.5.3
|
|
1012
1242
|
[0.5.2]: https://github.com/yaniswav/TopicForge/compare/v0.5.1...v0.5.2
|
|
1013
1243
|
[0.5.1]: https://github.com/yaniswav/TopicForge/compare/v0.5.0...v0.5.1
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: topicforge
|
|
3
|
+
Version: 0.5.5
|
|
4
|
+
Summary: ROS Topic Inspector & Bag Analyzer MCP server for AI agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/yaniswav/TopicForge
|
|
6
|
+
Project-URL: Repository, https://github.com/yaniswav/TopicForge
|
|
7
|
+
Project-URL: Issues, https://github.com/yaniswav/TopicForge/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/yaniswav/TopicForge/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Yanis ETHVIGNOT <ethvignot.yanis@gmail.com>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Yanis ETHVIGNOT
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: ai,claude,mcp,model-context-protocol,robotics,ros2
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
|
+
Requires-Dist: mcp<2,>=1.0.0
|
|
44
|
+
Requires-Dist: pydantic>=2.6
|
|
45
|
+
Provides-Extra: all
|
|
46
|
+
Requires-Dist: cyclonedds>=0.10; extra == 'all'
|
|
47
|
+
Provides-Extra: bags
|
|
48
|
+
Requires-Dist: rosbags>=0.9; extra == 'bags'
|
|
49
|
+
Provides-Extra: dds
|
|
50
|
+
Requires-Dist: cyclonedds>=0.10; extra == 'dds'
|
|
51
|
+
Provides-Extra: dds-cyclone
|
|
52
|
+
Requires-Dist: cyclonedds>=0.10; extra == 'dds-cyclone'
|
|
53
|
+
Provides-Extra: dev
|
|
54
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
55
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
56
|
+
Requires-Dist: rosbags>=0.9; extra == 'dev'
|
|
57
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# TopicForge
|
|
61
|
+
|
|
62
|
+
<!-- mcp-name: io.github.yaniswav/topicforge -->
|
|
63
|
+
|
|
64
|
+
[](https://pypi.org/project/topicforge/)
|
|
65
|
+
[](https://github.com/yaniswav/TopicForge/actions/workflows/ci.yml)
|
|
66
|
+
[](https://pypi.org/project/topicforge/)
|
|
67
|
+
[](https://github.com/yaniswav/TopicForge/blob/main/LICENSE)
|
|
68
|
+
[](https://github.com/yaniswav/TopicForge#security-model)
|
|
69
|
+
|
|
70
|
+
A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a ROS2 graph, recorded bag files and the DDS layer underneath ROS, without being able to publish to the bus or command a robot. It is read-only by **architecture**, not by configuration: there is no write path to misconfigure and no permission system to audit.
|
|
71
|
+
|
|
72
|
+
Without grounding, an LLM asked about a robot will invent topic names, message types and bag contents. TopicForge gives it **twelve typed tools** that return frozen Pydantic schemas, identical whether the server talks to a real robot or to its built-in mock fixtures. It is aimed at ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
73
|
+
|
|
74
|
+
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Every conformant vendor announces itself there, so a Cyclone participant also sees RTI Connext, OpenDDS, CoreDX and Dust DDS endpoints without any proprietary binding. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
75
|
+
|
|
76
|
+
## Quickstart
|
|
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.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install topicforge
|
|
82
|
+
TOPICFORGE_MODE=mock python -m topicforge
|
|
83
|
+
# Windows PowerShell: $env:TOPICFORGE_MODE="mock"; python -m topicforge
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The server speaks MCP over stdio and waits for a client, so wire it into one. For Claude Desktop, add to `claude_desktop_config.json`:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"topicforge": {
|
|
92
|
+
"command": "python",
|
|
93
|
+
"args": ["-m", "topicforge"],
|
|
94
|
+
"env": { "TOPICFORGE_MODE": "auto" }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. 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).
|
|
101
|
+
|
|
102
|
+
## Tools
|
|
103
|
+
|
|
104
|
+
All twelve tools are read-only. Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
|
|
105
|
+
|
|
106
|
+
| Tool | Purpose |
|
|
107
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------ |
|
|
108
|
+
| `health_check` | Environment and mode introspection. Always succeeds; reports `mode` next to `requested_mode` |
|
|
109
|
+
| `list_topics` | Discover the ROS2 graph |
|
|
110
|
+
| `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
|
|
111
|
+
| `sample_messages` | Peek recent messages on a ROS2 topic (count clamped to 50) |
|
|
112
|
+
| `analyze_bag` | Summarize a `.mcap` / `.db3` / `.bag` recording |
|
|
113
|
+
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
114
|
+
| `detect_qos_mismatches` | Incompatible QoS pairs between DDS readers and writers |
|
|
115
|
+
| `peek_dds_samples` | Raw DDS samples; structured on the three builtin discovery topics, presence-only on user topics |
|
|
116
|
+
| `participant_events` | Timeline of participant `discovered` / `lost` events |
|
|
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]`) |
|
|
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) |
|
|
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`).
|
|
122
|
+
|
|
123
|
+
## Modes
|
|
124
|
+
|
|
125
|
+
| Mode | When to use | Backend |
|
|
126
|
+
| ------ | ------------------------------------------------ | -------------------------- |
|
|
127
|
+
| `mock` | Development, demos, CI, screencasts | Deterministic fixtures |
|
|
128
|
+
| `live` | ROS2 sourced and on PATH, and/or a DDS backend | `ros2` CLI, DDS participant |
|
|
129
|
+
| `auto` | Detect what is available, else mock (default) | Best available |
|
|
130
|
+
|
|
131
|
+
`live` and `auto` degrade instead of failing: if neither `ros2` nor a DDS backend comes up, the server serves the mock fixtures and `health_check` reports `mode: "mock"` next to `requested_mode`. Only `TOPICFORGE_MODE=mock` forces fixtures unconditionally. The live ROS2 adapter shells out to the `ros2` CLI, so `rclpy` does not need to be importable.
|
|
132
|
+
|
|
133
|
+
## DDS backends
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pip install topicforge[dds] # Eclipse CycloneDDS ([dds-cyclone] is the same thing)
|
|
137
|
+
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`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 seven DDS tools to the DDS backend.
|
|
141
|
+
|
|
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, since the Pro tier is retired (see [`docs/pro.md`](docs/pro.md)). Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
143
|
+
|
|
144
|
+
## Configuration reference
|
|
145
|
+
|
|
146
|
+
| Variable | Default | Description |
|
|
147
|
+
| -------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| `TOPICFORGE_MODE` | `auto` | `mock`, `live` or `auto` |
|
|
149
|
+
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
150
|
+
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
|
|
151
|
+
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
|
|
152
|
+
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock`, `cyclone`, `fast`, `auto` (`opendds` and `dust` are stubs) |
|
|
153
|
+
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
154
|
+
|
|
155
|
+
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.
|
|
156
|
+
|
|
157
|
+
## Limitations
|
|
158
|
+
|
|
159
|
+
- **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.
|
|
160
|
+
- **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.
|
|
161
|
+
- **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.
|
|
162
|
+
- **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`.
|
|
163
|
+
- **Single domain.** The server observes the domain it joined at startup; changing it needs a restart.
|
|
164
|
+
- **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.
|
|
165
|
+
- **Fast DDS** serves no `list_endpoints`.
|
|
166
|
+
- **`sample_messages` (live)** runs `ros2 topic echo --csv --once` with a short timeout; a topic with no current publisher returns an empty sample. `timestamp_ns` is the message `header.stamp` for `Header`-stamped types and `0` for headerless ones.
|
|
167
|
+
- **`analyze_bag` (live)** parses `ros2 bag info` text and does not use `rosbags`; anomaly detection is mock-only. `peek_bag_samples` is the only tool that reads the file itself, through `rosbags`, and is served only by the ROS2 CLI adapter or the mock. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output.
|
|
168
|
+
- **Synchronous handlers.** The tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
|
|
169
|
+
- No streaming or push subscriptions: tools are strictly request/response.
|
|
170
|
+
|
|
171
|
+
The roadmap and the open work behind these limits are in [`docs/product-plan.md`](docs/product-plan.md).
|
|
172
|
+
|
|
173
|
+
## Telemetry
|
|
174
|
+
|
|
175
|
+
Opt-in, anonymous and **off by default**. When off, instrumentation returns the handler unchanged: no event is built, no transport is constructed, no network code runs (pinned by `tests/test_telemetry.py::test_build_app_off_makes_no_transport_calls`).
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
TOPICFORGE_TELEMETRY=on python -m topicforge
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
On-values: `on`, `1`, `true`, `yes`, `enabled`. Off-values: unset, `off`, `0`, `false`, `no`, `disabled`. Anything else is a configuration error, not a silent "off".
|
|
182
|
+
|
|
183
|
+
When on, each tool call emits one event with exactly six fields:
|
|
184
|
+
|
|
185
|
+
| Field | Example | Notes |
|
|
186
|
+
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
187
|
+
| `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
|
|
188
|
+
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
189
|
+
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
190
|
+
| `version` | `"0.5.5"` | TopicForge server version |
|
|
191
|
+
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
192
|
+
| `success` | `true` | Whether the handler returned or raised |
|
|
193
|
+
|
|
194
|
+
Never sent: topic names, message types or payloads, bag paths or contents, hostnames, usernames, IP addresses, environment variables, error messages. The field set is fenced by `tests/test_telemetry.py::test_payload_contains_only_whitelisted_keys`; adding a field requires updating this section. The default transport is a structured log line, there is no HTTP endpoint yet. The implementation is in [`src/topicforge/telemetry/`](src/topicforge/telemetry/).
|
|
195
|
+
|
|
196
|
+
## Security model
|
|
197
|
+
|
|
198
|
+
TopicForge is designed for **local trust**: it runs as a subprocess of your MCP client on a machine you control and inspects your own ROS2 graph, DDS domain and bag files. It is not hardened for adversarial inputs.
|
|
199
|
+
|
|
200
|
+
- `TOPICFORGE_ROS2_BIN` accepts an arbitrary path; treat it the way you treat `PATH`.
|
|
201
|
+
- `analyze_bag` and `peek_bag_samples` open whatever path the client passes (no workspace isolation, no symlink restriction).
|
|
202
|
+
- All `ros2` invocations use `subprocess.run` with an argument list, never `shell=True`. ROS2 topic names are validated against `^/[A-Za-z0-9_/]+$` first.
|
|
203
|
+
- The server loads no third-party code at startup. Until 0.5.2 it imported any installed `topicforge_pro` package; that hook was removed in 0.5.3 because it was an opening for a package of that name to add write tools.
|
|
204
|
+
- No outbound network calls unless telemetry is turned on.
|
|
205
|
+
|
|
206
|
+
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).
|
|
207
|
+
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
git clone https://github.com/yaniswav/TopicForge.git && cd TopicForge
|
|
212
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
|
|
213
|
+
pip install -e ".[dev]"
|
|
214
|
+
python -m ruff check src tests
|
|
215
|
+
python -m pytest
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
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` or `fastdds` 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).
|
|
219
|
+
|
|
220
|
+
## Upgrading
|
|
221
|
+
|
|
222
|
+
TopicForge is pre-1.0 and the 0.x releases changed things freely; [`CHANGELOG.md`](CHANGELOG.md) is the record. Two points matter if you are coming from an old install. Releases 0.3.0 to 0.5.2 are yanked, so `pip install -U topicforge` resolves to 0.5.3 or later. And since 0.5.3 the `[dds-fast]`, `[dds-opendds]`, `[dds-dust]` and `[dds-all-oss]` extras no longer exist, the DDS backend values `rti`, `opensplice`, `coredx` and `intercom` are rejected, and `[dds]` and `[all]` resolve to Cyclone only. Schema changes across 0.x were additive optional fields; a client that pins a JSON Schema with `additionalProperties: false` needs to regenerate it.
|
|
223
|
+
|
|
224
|
+
## Layout
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
src/topicforge/
|
|
228
|
+
server/ MCP bootstrap, build_app(settings)
|
|
229
|
+
tools/ thin FastMCP handlers, no backend logic
|
|
230
|
+
services/ input validation, orchestration, adapter factory
|
|
231
|
+
adapters/ ros2_live, ros2_mock, dds_cyclone, dds_fast, common/ (binding-free logic)
|
|
232
|
+
models/ frozen Pydantic schemas, the contract with MCP clients
|
|
233
|
+
config/ settings and mode resolution
|
|
234
|
+
telemetry/ opt-in, off by default
|
|
235
|
+
examples/ mock walkthroughs (*.md) and runnable live DDS examples (dds/)
|
|
236
|
+
scripts/ real-bus interop checks
|
|
237
|
+
docs/ guides, product plan
|
|
238
|
+
tests/ pytest suite, mock-only, no ROS2 or DDS required
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Layers are strictly separated: handlers never call `subprocess`, adapters are the only code that talks to a backend, and new backends implement the `MiddlewareAdapter` protocol in `adapters/base.py`.
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT, see [LICENSE](LICENSE). Commercial support and integration work: [`docs/pro.md`](docs/pro.md).
|