topicforge 0.5.4__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.
Files changed (133) hide show
  1. {topicforge-0.5.4 → topicforge-0.5.5}/CHANGELOG.md +123 -1
  2. {topicforge-0.5.4 → topicforge-0.5.5}/PKG-INFO +12 -8
  3. {topicforge-0.5.4 → topicforge-0.5.5}/README.md +11 -7
  4. topicforge-0.5.5/docs/DDS_QUICKSTART.md +126 -0
  5. {topicforge-0.5.4 → topicforge-0.5.5}/docs/TESTING.md +2 -2
  6. {topicforge-0.5.4 → topicforge-0.5.5}/docs/TROUBLESHOOTING.md +1 -1
  7. {topicforge-0.5.4 → topicforge-0.5.5}/docs/TUTORIEL.md +1 -1
  8. {topicforge-0.5.4 → topicforge-0.5.5}/docs/dds-interop-matrix.md +2 -2
  9. {topicforge-0.5.4 → topicforge-0.5.5}/docs/pro.md +1 -1
  10. {topicforge-0.5.4 → topicforge-0.5.5}/docs/product-plan.md +3 -3
  11. {topicforge-0.5.4 → topicforge-0.5.5}/examples/README.md +1 -1
  12. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/02_why_cant_they_talk/README.md +6 -3
  13. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/03_a_node_crashed/README.md +9 -8
  14. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/04_late_joiner_misses_data/README.md +2 -1
  15. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/08_crash_seen_from_inside/README.md +6 -5
  16. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/10_lidar_silent_after_driver_swap/README.md +8 -4
  17. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/11_who_talks_to_whom/README.md +2 -2
  18. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/12_safety_monitor_dropout/README.md +6 -3
  19. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/14_restart_loop/README.md +5 -4
  20. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/README.md +9 -5
  21. {topicforge-0.5.4 → topicforge-0.5.5}/pyproject.toml +1 -1
  22. topicforge-0.5.5/scripts/agent_eval/README.md +37 -0
  23. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/__init__.py +1 -1
  24. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/base.py +11 -2
  25. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/__init__.py +65 -1
  26. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/dds_helpers.py +50 -3
  27. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/dds_introspection.py +4 -4
  28. topicforge-0.5.5/src/topicforge/adapters/common/discovery_tracker.py +497 -0
  29. topicforge-0.5.5/src/topicforge/adapters/common/endpoints.py +394 -0
  30. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/lifecycle.py +48 -7
  31. topicforge-0.5.5/src/topicforge/adapters/common/qos_analyzer.py +304 -0
  32. topicforge-0.5.5/src/topicforge/adapters/common/qos_endpoints.py +73 -0
  33. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/qos_normalize.py +120 -7
  34. topicforge-0.5.5/src/topicforge/adapters/common/qos_scan.py +385 -0
  35. topicforge-0.5.5/src/topicforge/adapters/common/topic_filter.py +106 -0
  36. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/composite.py +24 -2
  37. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_cyclone/adapter.py +201 -179
  38. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_dust/adapter.py +12 -2
  39. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_fast/adapter.py +22 -20
  40. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_opendds/adapter.py +12 -2
  41. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/ros2_live/adapter.py +12 -2
  42. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/ros2_mock/adapter.py +13 -3
  43. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/ros2_mock/fixtures.py +124 -19
  44. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/models/__init__.py +16 -0
  45. topicforge-0.5.5/src/topicforge/models/schemas.py +1281 -0
  46. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/services/health.py +33 -1
  47. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/services/inspector.py +31 -2
  48. topicforge-0.5.5/src/topicforge/tools/handlers.py +601 -0
  49. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_composite_adapter.py +5 -4
  50. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_cyclone_adapter.py +74 -5
  51. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_dds_cross_vendor.py +5 -5
  52. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_dds_helpers.py +1 -1
  53. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_dds_qos_normalization.py +44 -0
  54. topicforge-0.5.5/tests/test_discovery_tracker.py +462 -0
  55. topicforge-0.5.5/tests/test_endpoints.py +444 -0
  56. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_example_node_spec.py +72 -0
  57. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_factory.py +4 -3
  58. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_health.py +22 -0
  59. topicforge-0.5.5/tests/test_honest_outputs.py +250 -0
  60. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_mock_adapter.py +17 -0
  61. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_qos_analyzer.py +205 -0
  62. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_qos_endpoints.py +18 -13
  63. topicforge-0.5.5/tests/test_qos_scan.py +388 -0
  64. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_tools_integration.py +9 -0
  65. topicforge-0.5.4/docs/DDS_QUICKSTART.md +0 -121
  66. topicforge-0.5.4/src/topicforge/adapters/common/qos_analyzer.py +0 -98
  67. topicforge-0.5.4/src/topicforge/adapters/common/qos_endpoints.py +0 -95
  68. topicforge-0.5.4/src/topicforge/models/schemas.py +0 -706
  69. topicforge-0.5.4/src/topicforge/tools/handlers.py +0 -453
  70. {topicforge-0.5.4 → topicforge-0.5.5}/.gitignore +0 -0
  71. {topicforge-0.5.4 → topicforge-0.5.5}/LICENSE +0 -0
  72. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/00_hello_pub_sub/README.md +0 -0
  73. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/01_who_is_on_the_bus/README.md +2 -2
  74. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/05_reliability_in_code/README.md +0 -0
  75. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
  76. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/07_deadline_in_code/README.md +0 -0
  77. {topicforge-0.5.4 → topicforge-0.5.5}/examples/dds/13_deadline_not_offered/README.md +0 -0
  78. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/README.md +0 -0
  79. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/cyclone_c/README.md +0 -0
  80. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
  81. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
  82. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/dust_py/README.md +0 -0
  83. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
  84. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/fast_py/README.md +0 -0
  85. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
  86. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/rti_c/README.md +0 -0
  87. {topicforge-0.5.4 → topicforge-0.5.5}/scripts/integration/publishers/rti_cpp/README.md +0 -0
  88. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/__main__.py +0 -0
  89. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/__init__.py +0 -0
  90. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
  91. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
  92. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/common/xtypes.py +0 -0
  93. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
  94. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_dust/__init__.py +0 -0
  95. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
  96. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/dds_opendds/__init__.py +0 -0
  97. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
  98. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
  99. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/config/__init__.py +0 -0
  100. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/config/settings.py +0 -0
  101. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/constants.py +0 -0
  102. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/server/__init__.py +0 -0
  103. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/server/app.py +0 -0
  104. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/services/__init__.py +0 -0
  105. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/services/bag_service.py +0 -0
  106. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/services/factory.py +0 -0
  107. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/telemetry/__init__.py +0 -0
  108. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/telemetry/client.py +0 -0
  109. {topicforge-0.5.4 → topicforge-0.5.5}/src/topicforge/tools/__init__.py +0 -0
  110. {topicforge-0.5.4 → topicforge-0.5.5}/tests/__init__.py +0 -0
  111. {topicforge-0.5.4 → topicforge-0.5.5}/tests/conftest.py +0 -0
  112. {topicforge-0.5.4 → topicforge-0.5.5}/tests/fixtures/csv_echo_imu.txt +0 -0
  113. {topicforge-0.5.4 → topicforge-0.5.5}/tests/fixtures/csv_echo_pose_multi.txt +0 -0
  114. {topicforge-0.5.4 → topicforge-0.5.5}/tests/integration/__init__.py +0 -0
  115. {topicforge-0.5.4 → topicforge-0.5.5}/tests/integration/test_real_bus.py +0 -0
  116. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_analyze_bag_multi_format.py +0 -0
  117. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_bag_service.py +0 -0
  118. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_cdr_decoder.py +0 -0
  119. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_config.py +0 -0
  120. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_dds_introspection.py +0 -0
  121. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_dds_schemas.py +0 -0
  122. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_dust_adapter.py +0 -0
  123. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_fast_adapter.py +0 -0
  124. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_inspector.py +0 -0
  125. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_lifecycle_buffer.py +0 -0
  126. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_live_adapter_parse.py +0 -0
  127. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_live_adapter_subprocess.py +0 -0
  128. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_metrics_buffer.py +0 -0
  129. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_opendds_adapter.py +0 -0
  130. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_peek_bag_samples.py +0 -0
  131. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_telemetry.py +0 -0
  132. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_topic_metrics.py +0 -0
  133. {topicforge-0.5.4 → topicforge-0.5.5}/tests/test_xtypes.py +0 -0
@@ -7,6 +7,127 @@ 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
+
10
131
  ## [0.5.4] - 2026-10-02
11
132
 
12
133
  First run of the DDS code against a live multi-vendor bus (Windows 11, a
@@ -1114,7 +1235,8 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
1114
1235
  - The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
1115
1236
  - `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
1116
1237
 
1117
- [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.4...HEAD
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
1118
1240
  [0.5.4]: https://github.com/yaniswav/TopicForge/compare/v0.5.3...v0.5.4
1119
1241
  [0.5.3]: https://github.com/yaniswav/TopicForge/compare/v0.5.2...v0.5.3
1120
1242
  [0.5.2]: https://github.com/yaniswav/TopicForge/compare/v0.5.1...v0.5.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: topicforge
3
- Version: 0.5.4
3
+ Version: 0.5.5
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
@@ -69,7 +69,7 @@ Description-Content-Type: text/markdown
69
69
 
70
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
71
 
72
- Without grounding, an LLM asked about a robot will invent topic names, message types and bag contents. TopicForge gives it **eleven typed tools** that return frozen Pydantic schemas, identical whether the server talks to a real robot or to its built-in mock fixtures. It is aimed at ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
72
+ 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
73
 
74
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
75
 
@@ -101,7 +101,7 @@ Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code:
101
101
 
102
102
  ## Tools
103
103
 
104
- All eleven tools are read-only. Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
104
+ 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
105
 
106
106
  | Tool | Purpose |
107
107
  | ----------------------- | ------------------------------------------------------------------------------------------------ |
@@ -116,6 +116,7 @@ All eleven tools are read-only. Every response except `health_check` carries `mo
116
116
  | `participant_events` | Timeline of participant `discovered` / `lost` events |
117
117
  | `topic_metrics` | Frequency, sequence-gap and latency schema; data only for builtin discovery topics |
118
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) |
119
120
 
120
121
  Walkthroughs against the mock, each with the exact tool calls and payloads, are in [`examples/`](examples/README.md). To run the DDS tools against a real bus with several programs and vendors, see [`examples/dds/README.md`](examples/dds/README.md) (`python examples/dds/run_all.py`).
121
122
 
@@ -136,7 +137,7 @@ pip install topicforge[dds] # Eclipse CycloneDDS ([dds-cycl
136
137
  TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
137
138
  ```
138
139
 
139
- `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the five ROS2 graph and bag tools to the CLI and the five DDS tools to the DDS backend.
140
+ `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the five ROS2 graph and bag tools to the CLI and the seven DDS tools to the DDS backend.
140
141
 
141
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).
142
143
 
@@ -156,9 +157,12 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
156
157
  ## Limitations
157
158
 
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.
159
- - **User-topic payloads are not decoded.** `peek_dds_samples` on a user topic reports that the topic is announced on the bus and returns one placeholder sample (`_decode_status="raw"`, empty `_raw_bytes_hex`); no traffic is read. Consequently `topic_metrics` only has data for the builtin discovery topics, its observed frequency is the cadence of your own `peek_dds_samples` calls, and latency and sequence gaps are `null`. It is a discovery-layer probe, not a publish-rate monitor.
160
+ - **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.
160
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`.
161
- - **DDS Security is not handled.** A participant without credentials sees an empty secure bus. `detect_qos_mismatches` covers Reliability, Durability, History and Deadline; Liveliness, Ownership and Partition are not checked.
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`.
162
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.
163
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.
164
168
  - **Synchronous handlers.** The tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
@@ -180,10 +184,10 @@ When on, each tool call emits one event with exactly six fields:
180
184
 
181
185
  | Field | Example | Notes |
182
186
  | ------------ | --------------- | ----------------------------------------------------------- |
183
- | `tool_name` | `"list_topics"` | One of the eleven tools, never argument values |
187
+ | `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
184
188
  | `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
185
189
  | `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
186
- | `version` | `"0.5.3"` | TopicForge server version |
190
+ | `version` | `"0.5.5"` | TopicForge server version |
187
191
  | `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
188
192
  | `success` | `true` | Whether the handler returned or raised |
189
193
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  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.
12
12
 
13
- Without grounding, an LLM asked about a robot will invent topic names, message types and bag contents. TopicForge gives it **eleven typed tools** that return frozen Pydantic schemas, identical whether the server talks to a real robot or to its built-in mock fixtures. It is aimed at ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
13
+ 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.
14
14
 
15
15
  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).
16
16
 
@@ -42,7 +42,7 @@ Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code:
42
42
 
43
43
  ## Tools
44
44
 
45
- All eleven tools are read-only. Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
45
+ 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.
46
46
 
47
47
  | Tool | Purpose |
48
48
  | ----------------------- | ------------------------------------------------------------------------------------------------ |
@@ -57,6 +57,7 @@ All eleven tools are read-only. Every response except `health_check` carries `mo
57
57
  | `participant_events` | Timeline of participant `discovered` / `lost` events |
58
58
  | `topic_metrics` | Frequency, sequence-gap and latency schema; data only for builtin discovery topics |
59
59
  | `peek_bag_samples` | Decoded samples from a recorded bag (needs `pip install topicforge[bags]`) |
60
+ | `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
60
61
 
61
62
  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`).
62
63
 
@@ -77,7 +78,7 @@ pip install topicforge[dds] # Eclipse CycloneDDS ([dds-cycl
77
78
  TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
78
79
  ```
79
80
 
80
- `TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the five ROS2 graph and bag tools to the CLI and the five DDS tools to the DDS backend.
81
+ `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.
81
82
 
82
83
  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).
83
84
 
@@ -97,9 +98,12 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
97
98
  ## Limitations
98
99
 
99
100
  - **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.
100
- - **User-topic payloads are not decoded.** `peek_dds_samples` on a user topic reports that the topic is announced on the bus and returns one placeholder sample (`_decode_status="raw"`, empty `_raw_bytes_hex`); no traffic is read. Consequently `topic_metrics` only has data for the builtin discovery topics, its observed frequency is the cadence of your own `peek_dds_samples` calls, and latency and sequence gaps are `null`. It is a discovery-layer probe, not a publish-rate monitor.
101
+ - **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.
102
+ - **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.
101
103
  - **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`.
102
- - **DDS Security is not handled.** A participant without credentials sees an empty secure bus. `detect_qos_mismatches` covers Reliability, Durability, History and Deadline; Liveliness, Ownership and Partition are not checked.
104
+ - **Single domain.** The server observes the domain it joined at startup; changing it needs a restart.
105
+ - **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.
106
+ - **Fast DDS** serves no `list_endpoints`.
103
107
  - **`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.
104
108
  - **`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.
105
109
  - **Synchronous handlers.** The tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
@@ -121,10 +125,10 @@ When on, each tool call emits one event with exactly six fields:
121
125
 
122
126
  | Field | Example | Notes |
123
127
  | ------------ | --------------- | ----------------------------------------------------------- |
124
- | `tool_name` | `"list_topics"` | One of the eleven tools, never argument values |
128
+ | `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
125
129
  | `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
126
130
  | `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
127
- | `version` | `"0.5.3"` | TopicForge server version |
131
+ | `version` | `"0.5.5"` | TopicForge server version |
128
132
  | `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
129
133
  | `success` | `true` | Whether the handler returned or raised |
130
134
 
@@ -0,0 +1,126 @@
1
+ # DDS quickstart
2
+
3
+ A short tour of TopicForge's DDS observability tools. The server joins the bus as a **read-only DDS-RTPS participant** and, by the OMG protocol guarantee, observes every conformant vendor through the builtin discovery topics. The `MiddlewareAdapter` protocol has no write method, so the MCP client cannot publish back on any backend. This guide does not assume ROS2.
4
+
5
+ **Validation status.** The Cyclone adapter has run against a live bus, with a Python / Cyclone participant and Dust DDS participants in Rust, Python and C, on Windows and in CI (Ubuntu and Windows, `.github/workflows/demo.yml`); see [`scripts/integration/README.md`](../scripts/integration/README.md). The Fast DDS adapter has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed yet. For runnable live scenarios (who is on the bus, why two nodes cannot talk, a node that crashed, a late joiner that misses data), see [`examples/dds/README.md`](../examples/dds/README.md). Multi-vendor positioning: [`dds-interop-matrix.md`](dds-interop-matrix.md).
6
+
7
+ ## 1. Mock mode
8
+
9
+ The mock fixtures expose every tool with no DDS SDK:
10
+
11
+ ```bash
12
+ pip install topicforge
13
+ TOPICFORGE_MODE=mock python -m topicforge
14
+ ```
15
+
16
+ - `list_participants(domain_id=0)` returns four participants: two CycloneDDS (`mock-robot`, `mock-laptop`), one Fast DDS (`mock-aerospace-node`) and one Dust DDS in Rust (`mock-rust-node`). The `vendor` field comes from the OMG vendor id: `cyclone`, `fast`, `rti`, `rti_micro`, `opensplice`, `opendds`, `coredx`, `intercom`, `dust`, `mock` or `unknown`.
17
+ - `detect_qos_mismatches(topic=None)` returns a `MismatchScan` with one report for `/dds/qos_mismatch`: a RELIABLE reader against a BEST_EFFORT writer.
18
+ - `list_endpoints()` returns the mock writers and readers with their QoS and a `by_topic` roll-up.
19
+ - `peek_dds_samples(topic="/dds/well_matched", count=3)` returns three deterministic samples. The mock only knows `/dds/well_matched`, `/dds/qos_mismatch`, `/dds/ddsforge/example` and `/dds/ddsforge/opaque`, and raises "Unknown DDS topic" for anything else.
20
+ - `topic_metrics(topic="/dds/heartbeat_10hz", window_seconds=60)` returns a pre-filled 10 Hz buffer (100 samples, no gaps, 50 ms latency). A live adapter behaves differently, see section 5 and [`examples/04-monitor-topic-frequency.md`](../examples/04-monitor-topic-frequency.md).
21
+
22
+ The mock illustrates payload shapes that no live adapter produces today, such as the `"full"` decode status on `/dds/ddsforge/example`.
23
+
24
+ ## 2. Live mode: choose a backend
25
+
26
+ **Cyclone** is the one with a PyPI install path:
27
+
28
+ ```bash
29
+ pip install topicforge[dds] # same as [dds-cyclone]
30
+ TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
31
+ ```
32
+
33
+ `cyclonedds` publishes wheels for CPython 3.10 to 3.13 on Windows, Linux and macOS (as of 11.0.1). A background thread reads the builtin DCPS topics through `BuiltinDataReader`. On a real bus `list_participants` reports each participant's `name` (EntityName QoS) and `hostname` (the `__Hostname` discovery property) when the remote participant sets them, and the vendor from the first two bytes of the GUID prefix; implementations that do not follow that convention (Dust DDS, RTI by default) show vendor `unknown`.
34
+
35
+ **Fast DDS** has no PyPI install path. The `fastdds` binding is not published, so TopicForge declares no extra for it. Build eProsima's [Fast-DDS-python](https://github.com/eProsima/Fast-DDS-python) from source (it needs the Fast DDS C++ libraries and SWIG), install it into the same environment, then `TOPICFORGE_DDS_BACKEND=fast`. The adapter was written against the 2.6.x API and has never run against a bus.
36
+
37
+ **Auto** (`TOPICFORGE_DDS_BACKEND=auto`) probes importable bindings in the order `fast`, `cyclone`, `mock`. With only the PyPI extra installed it resolves to Cyclone. `opendds` and `dust` are permanent stubs that always report unavailable and are not in the chain.
38
+
39
+ **Domain.** `TOPICFORGE_DDS_DOMAIN_ID` (`0..232`, default `0`) is joined at startup; the `domain_id` tool parameter exists for protocol uniformity only. Changing domains needs a restart.
40
+
41
+ **Commercial vendors.** `rti`, `opensplice`, `coredx` and `intercom` are rejected at startup with a configuration error. You do not need them to observe an RTI bus; for what a native RTI adapter would add (secure domains with vendor credentials, shared-memory-only deployments) see [`pro.md`](pro.md).
42
+
43
+ ## 3. The QoS mismatch scenario
44
+
45
+ The canonical "my subscriber does not receive" case. Against the mock:
46
+
47
+ ```
48
+ > Detect QoS mismatches on the current bus.
49
+
50
+ [tool call: detect_qos_mismatches]
51
+ {
52
+ "reports": [
53
+ {
54
+ "topic": "/dds/qos_mismatch",
55
+ "reader_guid": "...", "writer_guid": "...",
56
+ "reader_participant_name": "lidar_driver", "writer_participant_name": "nav_planner",
57
+ "incompatible_policies": ["Reliability"],
58
+ "severity": "incompatible",
59
+ "details": [{"policy": "Reliability", "requested": "RELIABLE",
60
+ "offered": "BEST_EFFORT", "rule": "a RELIABLE reader needs a RELIABLE writer"}],
61
+ "mode_effective": "mock"
62
+ }
63
+ ],
64
+ "not_matched": [],
65
+ "hints": ["Topic '/dds/ddsforge/opaque' has writers but no reader: there is no pair to compare."],
66
+ "pairs_checked": 3, "topics_scanned": 4,
67
+ "policies_checked": ["Reliability", "Durability", "Deadline", "Liveliness", "..."],
68
+ "policies_unchecked": ["Presentation: ...", "..."],
69
+ "mode_effective": "mock"
70
+ }
71
+ ```
72
+
73
+ The result is a `MismatchScan` envelope. Live adapters render GUIDs in dotted form (`xxxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx`). From this the agent can suggest a concrete fix: the writer is BEST_EFFORT but the reader requires RELIABLE, so relax the reader or upgrade the writer. The analysis is vendor-neutral pure code (`adapters/common/qos_analyzer.py`, `qos_scan.py`) over canonical `QosProfile` models, so it does not depend on which backend produced the discovery samples.
74
+
75
+ Order of checks per reader/writer pair: Partition first (wildcards `*` and `?` on one side match; wildcard against wildcard never does), then the type name, then the RxO policies. A pair separated by partition or type goes to `not_matched` and no QoS rule is run on it, so a partition split is never reported as a Reliability problem; an empty `reports` with a non-empty `not_matched` still means no data flows. RxO policies compared: Reliability, Durability, Deadline, Liveliness (kind and lease), LatencyBudget, Ownership (kind only), DestinationOrder, DataRepresentation. History (KEEP_ALL reader, KEEP_LAST writer) is reported as `risky`, not as an incompatibility. A policy a side did not announce is skipped and counted in a hint. `policies_unchecked` lists what stays out of scope (Presentation, XTypes assignability, runtime liveliness, anything not discoverable): discovery shows declared QoS, not runtime behavior, so an empty result is not proof the bus is healthy.
76
+
77
+ ## 4. Composite adapter and backend selection
78
+
79
+ When `ros2` is on PATH and a DDS backend starts, TopicForge builds both a `Ros2CliAdapter` and the DDS adapter behind a `CompositeAdapter`: the five ROS2 graph and bag tools go to the CLI, the seven DDS tools to the DDS backend. An explicit DDS backend (`cyclone`, `fast`, `auto`) is honoured in every mode except `mock`, including on a host without `ros2`.
80
+
81
+ | `TOPICFORGE_MODE` | `TOPICFORGE_DDS_BACKEND` | `ros2` on PATH | Active adapter | ROS2 graph and bag tools | DDS tools |
82
+ | --- | --- | --- | --- | --- | --- |
83
+ | `mock` | any | any | `MockAdapter` | fixtures | fixtures |
84
+ | `live` / `auto` | `mock` (default) | yes | `Ros2CliAdapter` | CLI | raise "DDS module is not active" |
85
+ | `live` / `auto` | `cyclone` / `fast` / `auto` | yes | `CompositeAdapter` | CLI | real binding |
86
+ | `live` / `auto` | `cyclone` / `fast` / `auto` | no | the DDS adapter alone | raise "DDS observability only" | real binding |
87
+ | `live` / `auto` | `cyclone` / `fast`, binding missing or participant fails | any | `Ros2CliAdapter` if `ros2` is on PATH, else `MockAdapter` | CLI, or fixtures | raise, or fixtures |
88
+ | `live` / `auto` | `mock` | no | `MockAdapter` | fixtures | fixtures |
89
+
90
+ A DDS problem never prevents startup: the server logs a warning naming the cause and keeps going. Only an invalid or removed configuration value stops it. `health_check` reports what was actually built: `mode` (can differ from `requested_mode`), `ros_backend` (`ros2_cli`, `mock`, `none`) and `dds_backend` (`cyclone`, `fast`, `mock`, `none`).
91
+
92
+ ## 5. Scope of the discovery tools
93
+
94
+ `list_endpoints` returns every announced writer and reader as a typed record: role, topic, type, owning participant (guid, name, vendor), structured QoS and announcement time. Its `by_topic` roll-up flags orphans (`no_reader`, `no_writer`) and lists endpoints of participants that left under `departed_writers` / `departed_readers`. TopicForge's own endpoints are excluded unless `include_observer` is set. Cyclone and the mock serve it; the Fast DDS backend raises "not supported yet". Use it first to find out who talks on which topic, and `detect_qos_mismatches` to learn why they do not match.
95
+
96
+ `peek_dds_samples` is structured on the three builtin discovery topics, with both backends. Builtin names carry no leading `/`:
97
+
98
+ ```
99
+ peek_dds_samples(topic="DCPSParticipant", count=5)
100
+ peek_dds_samples(topic="DCPSSubscription", count=10)
101
+ peek_dds_samples(topic="DCPSPublication", count=10)
102
+ ```
103
+
104
+ Each sample is the cached current discovery state, not a stream. The payload carries `vendor`, `guid`, `topic_name` and, for endpoints, `role`, `participant_guid`, `participant_name`, `type_id`, `qos`, `announced_ns` and `is_observer`. The sample `timestamp_ns` is the announcement time.
105
+
106
+ **User topics are not decoded.** For a user topic the tool returns count 0 and a `note`:
107
+
108
+ ```json
109
+ {
110
+ "topic": "/my/topic",
111
+ "count": 0,
112
+ "samples": [],
113
+ "mode_effective": "live",
114
+ "note": "payload decoding is disabled for DDS user topics, so no samples are returned; this does not mean the topic is silent. Use list_endpoints for the topic's presence, writers, readers and QoS"
115
+ }
116
+ ```
117
+
118
+ An empty result says nothing about traffic. The earlier decode path never worked on either backend and is disabled until it can be validated against a real bus. The `"full"` and `"partial"` decode statuses stay in the schema and the mock emits examples of them, but no live adapter produces them.
119
+
120
+ **`topic_metrics` only has data for the builtin topics.** For a user topic it returns `status="unsupported_user_topic"`, and its null fields are not a measurement. For a builtin topic, `frequency_hz_observed` is how often you called `peek_dds_samples` (one call yields `null`), `sequence_numbers_available` is `false` and the latency percentiles are `null`, because builtin samples carry no publish timestamp. `frequency_hz_declared` is `1 / deadline` for the shortest Deadline a writer announced, when there is one. Use it to watch discovery-layer churn, not to check a publish rate.
121
+
122
+ **Lifecycle.** On Cyclone a background thread (0.5 s period) reads the three builtin discovery topics and feeds the caches behind every discovery tool, so `participant_events` and `list_participants` do not depend on when you call them. A participant that cycles faster than the discovery history depth between two passes can still be missed. Event times come from DDS (`announced_ns`, `lost_ns`), with `time_source` saying which clock. A `lost` time is an upper bound of the death: a crash is only noticed when the lease expires, and a crash cannot be told from a clean leave. A writer that is alive but silent is not observable without reading its data. Fast DDS captures arrival and removal through listener callbacks.
123
+
124
+ ## 6. Open work
125
+
126
+ Wider real-bus validation (Fast DDS, RTI, OpenDDS, CoreDX, OpenSplice; re-enabling user-topic decoding depends on it), an opt-in data probe to catch a hung writer (planned for 0.5.6), and DDS Security, which is not handled at all: a participant without credentials sees an empty secure bus. The roadmap is in [`product-plan.md`](product-plan.md). Errors and fixes: [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md). Report what you see on a real domain at https://github.com/yaniswav/TopicForge/issues.
@@ -4,7 +4,7 @@ How to get a working ROS2 environment to point TopicForge at, and how to wire it
4
4
 
5
5
  | You want to... | Time | Path |
6
6
  | --- | --- | --- |
7
- | Try the eleven tools without installing ROS2 | 5 min | [Mock mode](#mock-mode) |
7
+ | Try the twelve tools without installing ROS2 | 5 min | [Mock mode](#mock-mode) |
8
8
  | Live mode on Windows | 45 min | [WSL2 + Humble](#wsl2--ros2-humble-windows-recommended) |
9
9
  | Live mode on Ubuntu | 20 min | [Linux native](#linux-native) |
10
10
  | Throwaway environment | 15 min | [Docker](#docker) |
@@ -102,4 +102,4 @@ TopicForge speaks MCP over stdio; any compliant client can spawn it with a comma
102
102
  }
103
103
  ```
104
104
 
105
- That is the Claude Desktop shape (`claude_desktop_config.json`); restart the app and the eleven tools appear under the hammer icon. For Claude Code run `claude mcp add topicforge -- topicforge`. Cursor, Continue and Cline accept the same stdio config. If the `topicforge` script is not on PATH, use `"command": "python", "args": ["-m", "topicforge"]`, or the absolute path of the binary inside your venv: desktop clients do not inherit your shell's PATH or venv activation.
105
+ That is the Claude Desktop shape (`claude_desktop_config.json`); restart the app and the twelve tools appear under the hammer icon. For Claude Code run `claude mcp add topicforge -- topicforge`. Cursor, Continue and Cline accept the same stdio config. If the `topicforge` script is not on PATH, use `"command": "python", "args": ["-m", "topicforge"]`, or the absolute path of the binary inside your venv: desktop clients do not inherit your shell's PATH or venv activation.
@@ -31,7 +31,7 @@ You called a ROS2 graph or bag tool (`list_topics`, `get_topic_info`, `sample_me
31
31
  2. Re-run with `TOPICFORGE_MODE=live` and your existing `TOPICFORGE_DDS_BACKEND`.
32
32
  3. Check `health_check`: `ros_backend` should be `"ros2_cli"` and `dds_backend` your vendor.
33
33
 
34
- If you want a DDS-only deployment, the message is expected: you use the five DDS tools and the two bag tools raise it. For offline work use `TOPICFORGE_MODE=mock`. The opposite error, "DDS module is not active", means `TOPICFORGE_DDS_BACKEND` is `mock` (the default) while a live adapter serves; set it to `cyclone`.
34
+ If you want a DDS-only deployment, the message is expected: you use the seven DDS tools and the two bag tools raise it. For offline work use `TOPICFORGE_MODE=mock`. The opposite error, "DDS module is not active", means `TOPICFORGE_DDS_BACKEND` is `mock` (the default) while a live adapter serves; set it to `cyclone`.
35
35
 
36
36
  ## "rosbags"
37
37
 
@@ -31,7 +31,7 @@ Add it to your MCP client. For Claude Desktop, edit `claude_desktop_config.json`
31
31
  }
32
32
  ```
33
33
 
34
- Restart Claude Desktop. All eleven tools appear under the hammer icon. Ask something like:
34
+ Restart Claude Desktop. All twelve tools appear under the hammer icon. Ask something like:
35
35
 
36
36
  > What topics are being published right now, and what message types do they carry?
37
37
 
@@ -25,7 +25,7 @@ Notice: a Rust implementation is in this list. That is the OMG-DDS promise: lang
25
25
 
26
26
  When you install TopicForge with DDS support (`pip install topicforge[dds]`, which installs the CycloneDDS Python binding), it joins the domain you point it at as a read-only participant. The Fast DDS adapter works the same way, but its Python binding is not on PyPI: you build it from eProsima's sources (see [`DDS_QUICKSTART.md`](DDS_QUICKSTART.md)).
27
27
 
28
- From there, TopicForge's discovery-based tools (`list_participants`, `detect_qos_mismatches`, `participant_events`, and `peek_dds_samples` on the builtin `DCPS*` topics) see **every conformant participant on the bus**, regardless of:
28
+ From there, TopicForge's discovery-based tools (`list_participants`, `list_endpoints`, `detect_qos_mismatches`, `participant_events`, and `peek_dds_samples` on the builtin `DCPS*` topics) see **every conformant participant on the bus**, regardless of:
29
29
 
30
30
  - **The vendor**: RTI Connext, OpenDDS, CoreDX, Fast DDS, Cyclone, InterCOM, Dust DDS, or any other DDS-RTPS conformant stack
31
31
  - **The host language**: C, C++11/14/17/20, Rust, Java, .NET, Python, Ada, anything with a binding
@@ -35,7 +35,7 @@ The two known interop gaps from the 2025-05 OMG report (Dust DDS <-> OpenDDS, Du
35
35
 
36
36
  ## Limits of this claim
37
37
 
38
- The claim is about **discovery**: which participants, readers and writers exist, and what QoS they announce. It does not extend to user-topic payloads: `peek_dds_samples` reports that a user topic is present on the bus but does not decode its contents, for any vendor. The project's own real-bus runs cover Cyclone and Dust DDS participants only (see [`../scripts/integration/README.md`](../scripts/integration/README.md)); RTI, OpenDDS, CoreDX, OpenSplice and Fast DDS have not been observed. For the rest the claim follows from the RTPS standard and from the OMG's published results above. Vendors that do not follow the RTPS vendor-id convention in the GUID prefix (Dust DDS, RTI by default) are reported with vendor `unknown` on Cyclone. Domains that use DDS Security are not observable at all, because TopicForge joins without credentials.
38
+ The claim is about **discovery**: which participants, readers and writers exist, and what QoS they announce. It does not extend to user-topic payloads: `peek_dds_samples` on a user topic returns count 0 and a note, and does not decode contents, for any vendor; `list_endpoints` shows the topic's writers, readers and QoS. The project's own real-bus runs cover Cyclone and Dust DDS participants only (see [`../scripts/integration/README.md`](../scripts/integration/README.md)); RTI, OpenDDS, CoreDX, OpenSplice and Fast DDS have not been observed. For the rest the claim follows from the RTPS standard and from the OMG's published results above. Vendors that do not follow the RTPS vendor-id convention in the GUID prefix (Dust DDS, RTI by default) are reported with vendor `unknown` on Cyclone. Domains that use DDS Security are not observable at all, because TopicForge joins without credentials.
39
39
 
40
40
  ## What TopicForge is not
41
41
 
@@ -22,7 +22,7 @@ Integration and adaptation to a specific ROS2 / DDS environment, native RTI Conn
22
22
 
23
23
  ## Known limitations
24
24
 
25
- DDS Security is not implemented on any adapter: if your domain requires authenticated or encrypted RTPS, TopicForge cannot join it today. `detect_qos_mismatches` covers Reliability, Durability, History and Deadline; Liveliness, Ownership and Partition are not checked.
25
+ DDS Security is not implemented on any adapter: if your domain requires authenticated or encrypted RTPS, TopicForge cannot join it today. `detect_qos_mismatches` covers Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation, with History as a risk; Presentation, XTypes assignability and runtime behavior are not checked.
26
26
 
27
27
  ## Contact
28
28
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  TopicForge is **the safety-first read-only MCP for ROS2 robotics**. Where general-purpose ROS-MCP servers let an LLM publish topics, call services, and command robots (useful for demos, untenable for production fleets, defense systems, or anything safety-certified), TopicForge is read-only by **architecture**, not by configuration. There is no write path to misconfigure, no permission system to audit, no liability conversation to have. The MCP client can see the robot stack; it cannot touch it.
10
10
 
11
- Concretely, the server exposes **eleven typed read-only tools today** (v0.5.3): the five ROS2-graph tools (`health_check`, `list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag`) plus the six DDS / observability tools shipped across v0.2.0-v0.4.0 (`list_participants`, `detect_qos_mismatches`, `peek_dds_samples`, `participant_events`, `topic_metrics`, `peek_bag_samples`). They are backed by a deterministic mock adapter (no ROS2/DDS required), a `ros2` CLI wrapper, or an OSS DDS participant (Eclipse CycloneDDS / eProsima Fast DDS). Outputs are frozen Pydantic schemas, stable across runtime modes. Telemetry is opt-in, six fields, zero user payload.
11
+ Concretely, the server exposes **twelve typed read-only tools today** (v0.5.5): the five ROS2-graph tools (`health_check`, `list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag`) plus the seven DDS / observability tools shipped across v0.2.0-v0.5.5 (`list_participants`, `list_endpoints`, `detect_qos_mismatches`, `peek_dds_samples`, `participant_events`, `topic_metrics`, `peek_bag_samples`). They are backed by a deterministic mock adapter (no ROS2/DDS required), a `ros2` CLI wrapper, or an OSS DDS participant (Eclipse CycloneDDS / eProsima Fast DDS). Outputs are frozen Pydantic schemas, stable across runtime modes. Telemetry is opt-in, six fields, zero user payload.
12
12
 
13
13
  The ROS-MCP category is no longer empty (see section 11 Risk register for the competitive landscape as of 2026-05-13). What TopicForge defends, and the rest of the pack will inherit, is the read-only-by-architecture stance and the production-quality engineering envelope around it: frozen schemas, mock-first development, telemetry contract pinned by tests, Windows-first cross-platform, no shell injection, deterministic outputs.
14
14
 
@@ -46,7 +46,7 @@ The strategic bet is **pack breadth via two focused products plus a modular surf
46
46
 
47
47
  **Motif of the pivot.** Earlier drafts of this plan sequenced a 3-to-5-MCP pack with a separate DDS observability MCP as MCP 02. The 2026-05-14 audit collapsed that into a 2-product strategy: TopicForge as an umbrella covering both middlewares, DatasetForge as the second standalone product. The binding constraints were (a) solo-maintenance cost of running two repos in parallel and (b) the fact that ROS2 and DDS are the same problem shape (a typed pub/sub graph that needs structured introspection) and the `RosAdapter` protocol already generalizes to a `MiddlewareAdapter` superset with zero rework. Two products instead of three reduces the surface area without losing coverage.
48
48
 
49
- The umbrella commits TopicForge to a broader scope than first drafted: the DDS module shipped **six** DDS / observability tools across v0.2.0-v0.4.0 (not the three originally scoped), taking the surface to **11 tools total**. The original 8-tool ceiling was formally revised: see the re-scope decision in section 11. The pack inherits the layer separation, mock-first development, opt-in telemetry, and read-only-by-architecture commitments from TopicForge. Pack-shared infrastructure extraction (telemetry, license, settings resolver into a `pack-template/` repo) becomes a non-decision at 2 products: fork-and-tweak from TopicForge to DatasetForge is acceptable ; revisit only if a third product is ever planned.
49
+ The umbrella commits TopicForge to a broader scope than first drafted: the DDS module shipped **six** DDS / observability tools across v0.2.0-v0.4.0 (not the three originally scoped), taking the surface to **11 tools total**, then **12** with `list_endpoints` (0.5.5, approved 2026-10-02 after a blind evaluation). The original 8-tool ceiling was formally revised: see the re-scope decision in section 11. The pack inherits the layer separation, mock-first development, opt-in telemetry, and read-only-by-architecture commitments from TopicForge. Pack-shared infrastructure extraction (telemetry, license, settings resolver into a `pack-template/` repo) becomes a non-decision at 2 products: fork-and-tweak from TopicForge to DatasetForge is acceptable ; revisit only if a third product is ever planned.
50
50
 
51
51
  ---
52
52
 
@@ -160,7 +160,7 @@ The risks worth tracking explicitly. Updated 2026-09-05 (previous pass: 2026-05-
160
160
  - **Cross-platform regressions on Windows -- CLOSED (2026-09-05).** Was: CI tested `ubuntu-latest` only, with Windows coverage manual-only before each release. Resolved in v0.5.0: `.github/workflows/ci.yml`'s matrix now runs `{ubuntu-latest, windows-latest} x {3.10, 3.11, 3.12, 3.13}` (3.10 added in 0.5.3, verified current). Residual, smaller risk: the Makefile still uses POSIX shell syntax, so users on plain PowerShell run the underlying commands listed in the README "Development" section.
161
161
  - **Telemetry trust.** Even opt-in telemetry can damage trust if the payload contract drifts. Mitigation: `tests/test_telemetry.py::test_payload_contains_only_whitelisted_keys` pins the six allowed keys. Any change requires a CHANGELOG entry and a README Telemetry section update in the same PR.
162
162
  - **Time / focus dilution.** A solo maintainer trying to drive two products (TopicForge umbrella + DatasetForge), commercial-support work for each, marketing, and the DDS module on top of TopicForge is the realistic risk. The 2026-05-14 pivot from a 3-to-5-MCP pack to a 2-product strategy reduced the surface but did not eliminate the risk. Mitigation: explicit phase gates (do not start Phase 2 until Phase 1 is shipped, do not act on the DDS module marketing until Phase 2 has shipped), though section 8 schedules `MiddlewareAdapter` protocol prep during Phase 1.
163
- - **Scope creep within the TopicForge umbrella.** Combining ROS2 + DDS introspection in one product risks bloating the tool surface beyond what a focused MCP should expose. **Re-scope decision (2026-07-08, ratified retroactively).** The register's original ceiling (5 ROS2 tools + at most 3 DDS tools, any 9th tool gated on a re-scope discussion documented *here* before code lands) was crossed during v0.4.0 **without that discussion being recorded in this register**, a governance gap surfaced by the 2026-07-08 external audit. The three tools that broke it are deliberate and were acknowledged in the CHANGELOG and `docs/projet-file/mcp-02-spec.md section 2` at ship time: `participant_events` (9th, v0.4.0 Phase 1), `topic_metrics` (10th, Phase 2), `peek_bag_samples` (11th, Phase 3). They are accepted; the revised ceiling is **11 tools**. A 12th tool now needs an explicit re-scope discussion documented in this register before code lands. Mitigation going forward: the `verify-change` skill's doc-drift step and the `docs-curator` sweep keep this register, `README.md`, and `CLAUDE.md` in sync so a ceiling break cannot ship undocumented again.
163
+ - **Scope creep within the TopicForge umbrella.** Combining ROS2 + DDS introspection in one product risks bloating the tool surface beyond what a focused MCP should expose. **Re-scope decision (2026-07-08, ratified retroactively).** The register's original ceiling (5 ROS2 tools + at most 3 DDS tools, any 9th tool gated on a re-scope discussion documented *here* before code lands) was crossed during v0.4.0 **without that discussion being recorded in this register**, a governance gap surfaced by the 2026-07-08 external audit. The three tools that broke it are deliberate and were acknowledged in the CHANGELOG and `docs/projet-file/mcp-02-spec.md section 2` at ship time: `participant_events` (9th, v0.4.0 Phase 1), `topic_metrics` (10th, Phase 2), `peek_bag_samples` (11th, Phase 3). They are accepted; the revised ceiling was **11 tools** and moved to **12** on 2026-10-02 for `list_endpoints`. A 13th tool now needs an explicit re-scope discussion documented in this register before code lands. Mitigation going forward: the `verify-change` skill's doc-drift step and the `docs-curator` sweep keep this register, `README.md`, and `CLAUDE.md` in sync so a ceiling break cannot ship undocumented again.
164
164
 
165
165
  ---
166
166
 
@@ -17,6 +17,6 @@ TOPICFORGE_MODE=mock python -m topicforge
17
17
  # Windows PowerShell: $env:TOPICFORGE_MODE="mock"; python -m topicforge
18
18
  ```
19
19
 
20
- Point any MCP client at the server with `"env": { "TOPICFORGE_MODE": "mock" }`. The mock exposes all 11 tools against deterministic fixtures, so every example is reproducible byte for byte.
20
+ Point any MCP client at the server with `"env": { "TOPICFORGE_MODE": "mock" }`. The mock exposes all 12 tools against deterministic fixtures, so every example is reproducible byte for byte.
21
21
 
22
22
  To run against a real bus, use `TOPICFORGE_MODE=live` and, for the DDS tools, `TOPICFORGE_DDS_BACKEND=cyclone` (see [`docs/DDS_QUICKSTART.md`](../docs/DDS_QUICKSTART.md)). The mock shows the shape of every response, not the behaviour of the live DDS adapters, which differ in two ways that examples 02 and 04 call out: user-topic payloads are not decoded, and `topic_metrics` only has data for the builtin discovery topics.
@@ -27,7 +27,9 @@ python run.py --hold # keep the programs running, ask your own MCP client
27
27
  ```
28
28
  [1] Which reader/writer pairs can never talk?
29
29
  -> detect_qos_mismatches
30
+ odom: matched (declared QoS): writer nav_planner -> reader lidar_driver
30
31
  scan: Reliability (incompatible): writer lidar_driver -> reader nav_planner
32
+ Reliability: reader asks RELIABLE, writer offers BEST_EFFORT
31
33
 
32
34
  [3] What does nav_planner actually receive on scan?
33
35
  -> nav_planner output
@@ -36,8 +38,8 @@ python run.py --hold # keep the programs running, ask your own MCP client
36
38
 
37
39
  [4] What does lidar_driver actually receive on odom?
38
40
  -> lidar_driver output
39
- [lidar_driver] rx odom: 10 in 1.0 s, last seq 39
40
- [lidar_driver] rx odom: 10 in 1.0 s, last seq 49
41
+ [lidar_driver] rx odom: 10 in 1.0 s, last seq 50
42
+ [lidar_driver] rx odom: 10 in 1.0 s, last seq 60
41
43
  ```
42
44
 
43
45
  - On `scan`, the planner **requests** RELIABLE delivery and the driver only
@@ -45,7 +47,8 @@ python run.py --hold # keep the programs running, ask your own MCP client
45
47
  offers, so DDS refuses the match.
46
48
  - On `odom` the QoS differ the other way: the writer offers RELIABLE and the
47
49
  reader asks for BEST_EFFORT. That is allowed (offering more than asked is
48
- fine), so TopicForge does not report it.
50
+ fine), so TopicForge does not report it as a mismatch. It lists it under
51
+ `matched`, the pairs DDS will connect on the declared QoS.
49
52
 
50
53
  ## Ask your agent
51
54
 
@@ -28,21 +28,22 @@ python run.py --hold # keep the programs running, ask your own MCP client
28
28
  [2] The LIDAR driver crashes. How long until the bus notices?
29
29
  -> list_participants
30
30
  killed lidar_driver
31
- lidar_driver reported as left after 14 s
31
+ lidar_driver reported as left after 10 s
32
32
 
33
33
  [3] What happened on the bus, in order?
34
34
  -> participant_events
35
35
  lost lidar_driver
36
- discovered lidar_driver
37
- discovered nav_planner
38
36
  discovered topicforge
37
+ discovered nav_planner
38
+ discovered lidar_driver
39
39
  ```
40
40
 
41
- - Cyclone DDS uses a 10 s lease by default. TopicForge polls the bus, so it
42
- reports the departure a few seconds after the lease expires.
43
- - TopicForge notices a departure when it is asked about the bus: it polls,
44
- it does not keep a continuous history in the background. An agent watching
45
- for crashes has to ask regularly.
41
+ - Cyclone DDS uses a 10 s lease by default, so the departure is reported when
42
+ the lease expires.
43
+ - TopicForge tracks discovery in the background, so the event is there when
44
+ you ask, and its time comes from DDS rather than from your question. The
45
+ `lost` time is an upper bound of the death: after a crash it is the lease
46
+ expiry, and a crash looks the same as a clean leave.
46
47
  - The lease is a per-vendor default you can configure. Dust DDS, for
47
48
  example, uses 100 s: a crashed Dust program stays "active" much longer.
48
49