topicforge 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topicforge-0.4.0/CHANGELOG.md +562 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/PKG-INFO +25 -5
- {topicforge-0.3.0 → topicforge-0.4.0}/README.md +13 -4
- topicforge-0.4.0/docs/pro.md +115 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/pyproject.toml +35 -1
- topicforge-0.4.0/scripts/integration/README.md +126 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/__init__.py +1 -1
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/base.py +38 -3
- topicforge-0.4.0/src/topicforge/adapters/common/__init__.py +52 -0
- topicforge-0.4.0/src/topicforge/adapters/common/cdr_decoder.py +186 -0
- topicforge-0.4.0/src/topicforge/adapters/common/lifecycle.py +251 -0
- topicforge-0.4.0/src/topicforge/adapters/common/metrics_buffer.py +257 -0
- topicforge-0.4.0/src/topicforge/adapters/common/xtypes.py +117 -0
- topicforge-0.4.0/src/topicforge/adapters/composite.py +102 -0
- topicforge-0.4.0/src/topicforge/adapters/dds_cyclone/adapter.py +720 -0
- topicforge-0.4.0/src/topicforge/adapters/dds_dust/__init__.py +12 -0
- topicforge-0.4.0/src/topicforge/adapters/dds_dust/adapter.py +97 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/dds_fast/adapter.py +224 -26
- topicforge-0.4.0/src/topicforge/adapters/dds_opendds/__init__.py +15 -0
- topicforge-0.4.0/src/topicforge/adapters/dds_opendds/adapter.py +103 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/ros2_live/adapter.py +25 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/ros2_mock/adapter.py +34 -0
- topicforge-0.4.0/src/topicforge/adapters/ros2_mock/fixtures.py +542 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/config/settings.py +116 -21
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/models/__init__.py +4 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/models/schemas.py +304 -8
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/server/app.py +2 -1
- topicforge-0.4.0/src/topicforge/services/bag_service.py +276 -0
- topicforge-0.4.0/src/topicforge/services/factory.py +265 -0
- topicforge-0.4.0/src/topicforge/services/health.py +96 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/services/inspector.py +52 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/tools/handlers.py +171 -14
- topicforge-0.4.0/tests/integration/__init__.py +16 -0
- topicforge-0.4.0/tests/integration/conftest.py +58 -0
- topicforge-0.4.0/tests/integration/scenarios/lifecycle_tracking.json +34 -0
- topicforge-0.4.0/tests/integration/scenarios/multi_vendor_basic.json +39 -0
- topicforge-0.4.0/tests/integration/scenarios/qos_mismatch_detection.json +47 -0
- topicforge-0.4.0/tests/integration/scenarios/topic_metrics_frequency.json +30 -0
- topicforge-0.4.0/tests/integration/scenarios/topic_metrics_sequence_gaps.json +34 -0
- topicforge-0.4.0/tests/integration/scenarios/xtypes_decode.json +29 -0
- topicforge-0.4.0/tests/integration/test_real_bus.py +81 -0
- topicforge-0.4.0/tests/integration/test_scenarios_schema.py +139 -0
- topicforge-0.4.0/tests/test_analyze_bag_multi_format.py +110 -0
- topicforge-0.4.0/tests/test_bag_service.py +134 -0
- topicforge-0.4.0/tests/test_cdr_decoder.py +214 -0
- topicforge-0.4.0/tests/test_composite_adapter.py +333 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_config.py +136 -2
- topicforge-0.4.0/tests/test_dust_adapter.py +49 -0
- topicforge-0.4.0/tests/test_factory.py +325 -0
- topicforge-0.4.0/tests/test_health.py +267 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_inspector.py +36 -0
- topicforge-0.4.0/tests/test_lifecycle_buffer.py +218 -0
- topicforge-0.4.0/tests/test_metrics_buffer.py +308 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_mock_adapter.py +100 -0
- topicforge-0.4.0/tests/test_opendds_adapter.py +58 -0
- topicforge-0.4.0/tests/test_peek_bag_samples.py +91 -0
- topicforge-0.4.0/tests/test_pro_hook.py +83 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_tools_integration.py +10 -0
- topicforge-0.4.0/tests/test_topic_metrics.py +105 -0
- topicforge-0.4.0/tests/test_xtypes.py +77 -0
- topicforge-0.3.0/CHANGELOG.md +0 -154
- topicforge-0.3.0/docs/pro.md +0 -80
- topicforge-0.3.0/src/topicforge/adapters/common/__init__.py +0 -17
- topicforge-0.3.0/src/topicforge/adapters/dds_cyclone/adapter.py +0 -384
- topicforge-0.3.0/src/topicforge/adapters/ros2_mock/fixtures.py +0 -298
- topicforge-0.3.0/src/topicforge/services/factory.py +0 -129
- topicforge-0.3.0/src/topicforge/services/health.py +0 -65
- topicforge-0.3.0/tests/test_health.py +0 -126
- {topicforge-0.3.0 → topicforge-0.4.0}/.gitignore +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/LICENSE +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/docs/DDS_QUICKSTART.md +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/docs/MIGRATION_v0.1_to_v0.2.md +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/docs/MIGRATION_v0.2_to_v0.3.md +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/docs/TESTING.md +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/docs/dds-interop-matrix.md +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/docs/product-plan.md +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/__main__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/common/dds_helpers.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/services/constants.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/__init__.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/conftest.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/fixtures/csv_echo_imu.txt +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/fixtures/csv_echo_pose_multi.txt +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_cyclone_adapter.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_dds_cross_vendor.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_dds_helpers.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_dds_schemas.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_fast_adapter.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.3.0 → topicforge-0.4.0}/tests/test_telemetry.py +0 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to TopicForge are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.4.0]
|
|
9
|
+
|
|
10
|
+
### Sprint v0.4.0 — Phase 3 (bag analysis multi-format)
|
|
11
|
+
|
|
12
|
+
> Branch `feat/v0.4.0-phase3-bag-analysis-mcap-db3-rosbag`. Three
|
|
13
|
+
> sub-milestones (3.1 CDR refactor, 3.2 bag_service + enriched
|
|
14
|
+
> analyze_bag, 3.3 peek_bag_samples). v0.3.0 stays the live PyPI
|
|
15
|
+
> version ; no version bump in this branch — manual maintainer step
|
|
16
|
+
> after final review.
|
|
17
|
+
|
|
18
|
+
#### Refactored (sub-milestone 3.1 — CDR decoder commun)
|
|
19
|
+
|
|
20
|
+
- **6 vendor-agnostic helpers extracted** from `dds_cyclone/adapter.py`
|
|
21
|
+
into `adapters/common/cdr_decoder.py`: `decode_dynamic_sample`,
|
|
22
|
+
`iter_field_names`, `decode_field_value`, `dynamic_type_name`,
|
|
23
|
+
`extract_seq_from_payload`, `extract_publish_ns_from_payload`. The
|
|
24
|
+
Cyclone module keeps `_underscore` aliases pointing at the new
|
|
25
|
+
common functions, so every pre-Phase-3 call site keeps working
|
|
26
|
+
without rewrites.
|
|
27
|
+
- **22 new pure-logic tests** (`tests/test_cdr_decoder.py`) pin the
|
|
28
|
+
extracted contract in isolation. The 22 Phase 1.5 XTypes Cyclone
|
|
29
|
+
tests (gated by `requires_cyclonedds`) remain green through the
|
|
30
|
+
full pipeline on hosts with the SDK installed — refactor is
|
|
31
|
+
transparent.
|
|
32
|
+
|
|
33
|
+
#### Added (sub-milestone 3.2 — BagService + enriched analyze_bag)
|
|
34
|
+
|
|
35
|
+
- **`BagService`** (`services/bag_service.py`) — facade wrapping the
|
|
36
|
+
`rosbags` library (Apache 2.0, pure-Python). Two methods:
|
|
37
|
+
`analyze(path)` (stats + format detection) and `peek_samples(path,
|
|
38
|
+
topic, count)` (decoded samples via the shared cdr_decoder).
|
|
39
|
+
Lazy import of rosbags ; methods raise a clear AdapterError when
|
|
40
|
+
the library is absent (caller can fall back to v0.3.0 text-parse
|
|
41
|
+
behavior on the analyze path ; sample peek requires the library).
|
|
42
|
+
- **`BagAnalysis` schema enriched** (`models/schemas.py`) with four
|
|
43
|
+
**additive optional fields**:
|
|
44
|
+
- `bag_format: Literal["mcap","db3","bag","unknown"] | None` —
|
|
45
|
+
container format detected from the file extension
|
|
46
|
+
- `samples_decoded_count: int` — total decoded sample count
|
|
47
|
+
(analyze keeps this at 0 ; peek_bag_samples does the decoding)
|
|
48
|
+
- `recording_duration_ns: int | None` — recording duration in ns
|
|
49
|
+
from the bag's index when readable
|
|
50
|
+
- `participants_recorded: list[ParticipantInfo]` — DDS participants
|
|
51
|
+
embedded in the bag container (MCAP can ; .db3 / .bag generally
|
|
52
|
+
don't — empty list is the common case)
|
|
53
|
+
- **`MOCK_BAG_ANALYSIS` updated** with deterministic enriched values.
|
|
54
|
+
New `MOCK_BAG_SAMPLES` dict + `mock_bag_samples_for(topic, count)`
|
|
55
|
+
helper for the upcoming peek_bag_samples tool.
|
|
56
|
+
- **`[bags]` pyproject extra** (`rosbags>=0.9`) — NOT bundled in
|
|
57
|
+
`[all]` for granular install. New `requires_rosbags` pytest
|
|
58
|
+
marker.
|
|
59
|
+
|
|
60
|
+
#### Added (sub-milestone 3.3 — peek_bag_samples MCP tool)
|
|
61
|
+
|
|
62
|
+
- **`peek_bag_samples(path, topic, count) -> SampleResult`** — the
|
|
63
|
+
11th MCP tool, third explicit ceiling break. Returns up to
|
|
64
|
+
`count` decoded samples for `topic` from a recorded bag file.
|
|
65
|
+
Distinct from `peek_dds_samples` (live bus) and `sample_messages`
|
|
66
|
+
(ROS2 graph live peek) — this tool is **post-mortem inspection**.
|
|
67
|
+
Same `SampleResult` shape across all three tools so LLM
|
|
68
|
+
consumers read one envelope. Each sample's `_decode_status`
|
|
69
|
+
annotation carries over from the shared cdr_decoder.
|
|
70
|
+
- **`MiddlewareAdapter` protocol** gains
|
|
71
|
+
`peek_bag_samples(path, topic, count) -> SampleResult`. All
|
|
72
|
+
existing adapters implement it: Mock via fixtures, Ros2CliAdapter
|
|
73
|
+
via `BagService`, Cyclone / Fast / OpenDDS / Dust raise their
|
|
74
|
+
existing roadmap errors (DDS-only adapters don't do bag analysis).
|
|
75
|
+
- **CompositeAdapter** delegates `peek_bag_samples` to the ROS half
|
|
76
|
+
(bag analysis is ROS-native — MCAP is the canonical ROS2
|
|
77
|
+
recording format).
|
|
78
|
+
- **10 new tests** in `tests/test_peek_bag_samples.py` (tool-level
|
|
79
|
+
via Inspector + MockAdapter). Plus extensions to
|
|
80
|
+
`test_composite_adapter`, `test_factory`, `test_opendds_adapter`,
|
|
81
|
+
`test_dust_adapter`, `test_tools_integration` (MVP_TOOLS grows
|
|
82
|
+
from 10 → 11).
|
|
83
|
+
- **`docs/projet-file/mcp-02-spec.md` §2** — ceiling note updated
|
|
84
|
+
to 11 tools.
|
|
85
|
+
|
|
86
|
+
#### Notes (Phase 3)
|
|
87
|
+
|
|
88
|
+
- **Bag analysis is offline-only.** `analyze_bag` and
|
|
89
|
+
`peek_bag_samples` read files ; they do not introspect the live
|
|
90
|
+
bus. For live introspection use the existing tool set
|
|
91
|
+
(`list_topics`, `peek_dds_samples`, etc.).
|
|
92
|
+
- **rosbags requirement.** `BagService.peek_samples` strictly requires
|
|
93
|
+
`pip install topicforge[bags]` — no silent fallback for sample
|
|
94
|
+
peek. `analyze_bag` retains the v0.3.0 `ros2 bag info` text-parse
|
|
95
|
+
fallback on Ros2CliAdapter when rosbags is absent ; the enriched
|
|
96
|
+
fields populate at their safe defaults in that path.
|
|
97
|
+
- **Backward compat.** Every v0.3.0 / Phase 1 / 1.5 / 2 test passes
|
|
98
|
+
unchanged. BagAnalysis additive fields preserve the wire contract
|
|
99
|
+
for v0.3.0 consumers ignoring them.
|
|
100
|
+
- **The version bump and tag are deliberate manual maintainer
|
|
101
|
+
steps** after Phase 3 review. This branch leaves `pyproject.toml`
|
|
102
|
+
at `0.3.0`, `__version__` at `"0.3.0"`, and the
|
|
103
|
+
`## [Unreleased]` heading intact.
|
|
104
|
+
|
|
105
|
+
### Sprint v0.4.0 — Phase 2 (temporal metrics + real-bus rig)
|
|
106
|
+
|
|
107
|
+
> Branch `feat/v0.4.0-phase2-metrics-and-realbus-testing`. Two
|
|
108
|
+
> sub-milestones (2.1 metrics, 2.2 real-bus rig). v0.3.0 stays the
|
|
109
|
+
> live PyPI version ; no version bump.
|
|
110
|
+
|
|
111
|
+
#### Added (sub-milestone 2.1 — topic_metrics)
|
|
112
|
+
|
|
113
|
+
- **`topic_metrics(topic, window_seconds, domain_id)` MCP tool**
|
|
114
|
+
(the 10th — second explicit ceiling break after `participant_events`
|
|
115
|
+
in Phase 1). Returns a `TopicMetrics` payload with
|
|
116
|
+
`samples_observed`, `frequency_hz_observed` (and `_declared` from
|
|
117
|
+
QoS Deadline when known), `sequence_gaps_count`, `latency_ns_p50` /
|
|
118
|
+
`_p95` / `_p99`, and boolean availability flags for each
|
|
119
|
+
conditional metric. Window range: 1..3600 seconds, default 60.
|
|
120
|
+
- **`MetricsBuffer`** (`adapters/common/metrics_buffer.py`) — pure-
|
|
121
|
+
Python logic, RLock-protected per-topic deque with
|
|
122
|
+
`MAX_SAMPLES_PER_TOPIC=1000` drop-oldest cap. Pure-Python percentile
|
|
123
|
+
computation (no NumPy dependency added). Sequence gap counting
|
|
124
|
+
tolerates out-of-order arrivals and dedupes duplicates.
|
|
125
|
+
- **`TopicMetrics`** Pydantic schema (`models/schemas.py`) — frozen,
|
|
126
|
+
`extra="forbid"`, every field documented with the
|
|
127
|
+
None/zero-on-unavailable semantics that surfaces partial data
|
|
128
|
+
cleanly to LLM callers.
|
|
129
|
+
- **Cyclone + Fast adapter integration** — `_peek_builtin` and
|
|
130
|
+
`_peek_user_topic` paths now call `self._metrics.record(...)` for
|
|
131
|
+
each sample they surface. Sequence number and publish timestamp
|
|
132
|
+
extracted best-effort from the decoded payload via two new helpers
|
|
133
|
+
in `dds_cyclone/adapter.py`. **Opportunistic fill caveat** —
|
|
134
|
+
neither `cyclonedds` nor `fastdds` 2.6.x Python bindings expose
|
|
135
|
+
at-sample-receive callbacks, so the metrics buffer accumulates
|
|
136
|
+
ONLY as `peek_dds_samples` is exercised. Tool description surfaces
|
|
137
|
+
this to the LLM.
|
|
138
|
+
- **Mock fixture** (`/dds/heartbeat_10hz`, 100 samples spaced 100 ms
|
|
139
|
+
apart with synthetic 50 ms latency and sequence 0..99) plus a
|
|
140
|
+
singleton topic and a cross-domain topic for filter testing.
|
|
141
|
+
- **~30 new tests**: `tests/test_metrics_buffer.py` (~22 pure-logic
|
|
142
|
+
tests covering the helpers, ring overflow, multi-topic isolation,
|
|
143
|
+
domain filtering, thread-safety smoke) + `tests/test_topic_metrics.py`
|
|
144
|
+
(~12 tool-level tests via Inspector + MockAdapter).
|
|
145
|
+
|
|
146
|
+
#### Changed (sub-milestone 2.1)
|
|
147
|
+
|
|
148
|
+
- **`MiddlewareAdapter` protocol** gains `topic_metrics(topic,
|
|
149
|
+
window_seconds, domain_id)`. All existing adapters implement it:
|
|
150
|
+
Cyclone + Fast via the new buffer, Mock via fixtures, Ros2CliAdapter
|
|
151
|
+
/ OpenDDS stub / Dust stub raise their existing roadmap errors.
|
|
152
|
+
- **`AdapterName` Literal** unchanged (no new vendor) ; `MVP_TOOLS`
|
|
153
|
+
test set grows from 9 to 10.
|
|
154
|
+
|
|
155
|
+
#### Added (sub-milestone 2.2 — real-bus rig)
|
|
156
|
+
|
|
157
|
+
- **`tests/integration/`** — six scenario JSON files exercising
|
|
158
|
+
the multi-vendor OMG-DDS-RTPS claim against real publishers:
|
|
159
|
+
`multi_vendor_basic`, `lifecycle_tracking`, `qos_mismatch_detection`,
|
|
160
|
+
`xtypes_decode`, `topic_metrics_frequency`,
|
|
161
|
+
`topic_metrics_sequence_gaps`. Each scenario declares
|
|
162
|
+
`required_vendors` so the runner can skip cleanly when a binding
|
|
163
|
+
is missing.
|
|
164
|
+
- **`tests/integration/test_scenarios_schema.py`** — pure-Python
|
|
165
|
+
validation of every scenario's structure. Runs in default
|
|
166
|
+
`make check` (8 tests). No SDK, no Docker required.
|
|
167
|
+
- **`tests/integration/test_real_bus.py`** — parametrized integration
|
|
168
|
+
tests, marked `@pytest.mark.integration` ; **gated out of the
|
|
169
|
+
default pytest invocation**. Runs only with `pytest -m integration`
|
|
170
|
+
or the labeled CI workflow.
|
|
171
|
+
- **`scripts/integration/scenarios_runner.py`** — Python entry point
|
|
172
|
+
that probes locally installed DDS bindings, dispatches scenarios,
|
|
173
|
+
and reports pass/fail per assertion. Pragmatic partial-run: missing
|
|
174
|
+
vendors are skipped with a clear `[skipped]` log rather than failing
|
|
175
|
+
the whole batch (D6).
|
|
176
|
+
- **`scripts/integration/publishers/`** — per-vendor minimal publishers
|
|
177
|
+
(`cyclone_publisher.py`, `fast_publisher.py`, `opendds_publisher.py`).
|
|
178
|
+
Symmetric CLI surface: `--topic`, `--rate-hz`, `--duration-s`,
|
|
179
|
+
`--domain`, `--gap-at-seq`. OpenDDS publisher is a documented
|
|
180
|
+
scaffold (no PyPI binding yet).
|
|
181
|
+
- **`scripts/integration/run-local.{sh,ps1}`** — standalone entry
|
|
182
|
+
points for Linux/macOS and Windows. No Docker required.
|
|
183
|
+
- **`scripts/integration/docker-compose.yml` + per-vendor Dockerfiles** —
|
|
184
|
+
full multi-vendor rig for CI runs and maintainer validation.
|
|
185
|
+
Cyclone + Fast images build against the OSS PyPI bindings ; OpenDDS
|
|
186
|
+
image documents the BYO path. `topicforge` observer image installs
|
|
187
|
+
from repo source plus `[dds]` extra.
|
|
188
|
+
- **`.github/workflows/integration.yml`** — manual label trigger
|
|
189
|
+
(`integration-tests`). Runs schema validation, builds the compose
|
|
190
|
+
stack, sleeps 30 s for discovery, runs `pytest -m integration`,
|
|
191
|
+
tears down. Default `ci.yml` is untouched.
|
|
192
|
+
- **New pytest marker** `integration` — added to `pyproject.toml`
|
|
193
|
+
alongside the existing `requires_*` markers.
|
|
194
|
+
|
|
195
|
+
#### Notes (sub-milestone 2.2)
|
|
196
|
+
|
|
197
|
+
- **Validation reality.** The OSS-CI default pipeline lint-validates
|
|
198
|
+
scenario JSON structure, YAML/PowerShell/bash syntax, and the
|
|
199
|
+
runner's dispatch logic. It does NOT pull / build / run Docker
|
|
200
|
+
images. The maintainer validates the live publisher path locally
|
|
201
|
+
before merging Phase 2.2 ; the integration CI workflow is the
|
|
202
|
+
shared validation surface once an SDK-rich runner host is
|
|
203
|
+
available.
|
|
204
|
+
- **OpenDDS publisher is a scaffold.** `pyopendds` is not on PyPI as
|
|
205
|
+
of 2026-05-14 — the publisher script exits with an actionable
|
|
206
|
+
error message, and the scenarios runner skips OpenDDS scenarios
|
|
207
|
+
cleanly. Scenarios requiring OpenDDS (`multi_vendor_basic`) still
|
|
208
|
+
run partial coverage of the other vendors.
|
|
209
|
+
- **Real-bus assertion evaluation is structural at Phase 2.2.** The
|
|
210
|
+
runner dispatches scenarios and reports per-assertion status, but
|
|
211
|
+
the full per-assertion verification logic (parsing TopicForge tool
|
|
212
|
+
outputs, comparing against scenario `expect` clauses) is the
|
|
213
|
+
maintainer's follow-up. The scaffold is ready to receive that
|
|
214
|
+
wiring without re-touching the surrounding files.
|
|
215
|
+
|
|
216
|
+
### Sprint v0.4.0 — Phase 1 (DDS observability core)
|
|
217
|
+
|
|
218
|
+
> Internal-only ; the branch `feat/v0.4.0-phase1-observability-core` is
|
|
219
|
+
> merging to `main` between v0.3.0 and v0.4.0. **No version bump in
|
|
220
|
+
> this section** — `pyproject.toml`/`__version__` stay at `0.3.0` until
|
|
221
|
+
> Phase 3 closes.
|
|
222
|
+
|
|
223
|
+
#### Added
|
|
224
|
+
|
|
225
|
+
- **`CompositeAdapter` (`adapters/composite.py`).** New wrapper that
|
|
226
|
+
routes the 4 ROS2 protocol methods to a `Ros2CliAdapter` and the 3
|
|
227
|
+
DDS methods (+ `participant_events`) to a DDS adapter, so a single
|
|
228
|
+
process serves all 9 tools when `TOPICFORGE_MODE=live` and
|
|
229
|
+
`TOPICFORGE_DDS_BACKEND=cyclone|fast` are configured together. The
|
|
230
|
+
`name` collapses to `"ros2_cli+cyclone"` or `"ros2_cli+fast"` ;
|
|
231
|
+
`effective_mode` reports `"live"` when either half is live.
|
|
232
|
+
- **Participant lifecycle tracking.** `ParticipantInfo` gains four
|
|
233
|
+
additive optional fields: `first_seen_ns`, `last_seen_ns`, `status`
|
|
234
|
+
(`active`/`left`/`unknown`), `seen_count`. v0.3.0 producers and
|
|
235
|
+
fixtures remain valid because every new field has a safe default.
|
|
236
|
+
- **`LifecycleBuffer`** (`adapters/common/lifecycle.py`) — RLock-protected
|
|
237
|
+
ring buffer (cap 200 events, drop-oldest) shared by Cyclone (polling
|
|
238
|
+
reconciliation) and Fast DDS (listener callbacks). Pure logic, no DDS
|
|
239
|
+
dependency ; testable without any SDK installed.
|
|
240
|
+
- **`participant_events` MCP tool (the 9th).** New read-only tool that
|
|
241
|
+
returns DDS participant `discovered`/`lost` events over a configurable
|
|
242
|
+
window (default 300s, range 1..86400, hard cap 200 events, newest
|
|
243
|
+
first). Breaks the 8-tool ceiling documented in
|
|
244
|
+
`docs/projet-file/mcp-02-spec.md §2` ; acknowledged in this Phase 1
|
|
245
|
+
scope.
|
|
246
|
+
- **`HealthReport.ros_backend`** (`Literal["mock","ros2_cli","none"]`,
|
|
247
|
+
default `"none"`). Symmetric to the existing `dds_backend` ; lets
|
|
248
|
+
clients distinguish the ROS and DDS halves of a composed runtime.
|
|
249
|
+
|
|
250
|
+
#### Changed
|
|
251
|
+
|
|
252
|
+
- **Factory decision tree** (`services/factory.py`). Live mode now
|
|
253
|
+
attempts to build both a ROS2 CLI adapter and a DDS adapter and
|
|
254
|
+
wraps them in a `CompositeAdapter` when both succeed. Graceful
|
|
255
|
+
degradation paths preserved: DDS missing → ROS2 CLI alone (v0.3.0
|
|
256
|
+
behavior) ; ROS2 CLI missing → DDS-only adapter ; neither
|
|
257
|
+
available → MockAdapter.
|
|
258
|
+
- **`MiddlewareAdapter` protocol** (`adapters/base.py`). Gains
|
|
259
|
+
`participant_events(domain_id, lookback_seconds)`. All adapters
|
|
260
|
+
implement it: Mock returns deterministic fixtures, Cyclone and Fast
|
|
261
|
+
read from their `LifecycleBuffer`, Ros2CliAdapter raises
|
|
262
|
+
`AdapterError(_DDS_MODULE_INACTIVE_MSG)`. `AdapterName` Literal
|
|
263
|
+
widened with the two composite tags.
|
|
264
|
+
- **`CycloneDdsAdapter.list_participants`** now feeds the
|
|
265
|
+
`LifecycleBuffer` (polling delta reconciliation per call) and
|
|
266
|
+
returns enriched `ParticipantInfo` snapshots with lifecycle fields.
|
|
267
|
+
Discovery-sample mapping unchanged ; the returned shape is a
|
|
268
|
+
superset of v0.3.0.
|
|
269
|
+
- **`FastDdsAdapter._DiscoveryListener`** now feeds the
|
|
270
|
+
`LifecycleBuffer` from `on_participant_discovery` callbacks
|
|
271
|
+
(arrival AND removal events captured natively, no polling
|
|
272
|
+
reconciliation needed).
|
|
273
|
+
|
|
274
|
+
#### Notes
|
|
275
|
+
|
|
276
|
+
- **Cyclone lifecycle caveat.** Cyclone's lifecycle log is updated only
|
|
277
|
+
when `list_participants` (or any internal poll of the
|
|
278
|
+
`DCPSParticipant` builtin reader) is called. A participant that
|
|
279
|
+
joined and left between two tool calls is invisible. The
|
|
280
|
+
`participant_events` tool description makes this explicit.
|
|
281
|
+
- **Backward compatibility.** Zero v0.3.0 tests regress. Pydantic
|
|
282
|
+
`extra="forbid"` is preserved on every model ; the four new
|
|
283
|
+
`ParticipantInfo` fields have safe defaults so producers built
|
|
284
|
+
against v0.3.0 schemas keep working. `ParticipantEvent` is a new
|
|
285
|
+
model — clients ignoring it continue to work.
|
|
286
|
+
|
|
287
|
+
#### Added (continued — sub-milestone 1.3)
|
|
288
|
+
|
|
289
|
+
- **`peek_dds_samples` on user-defined topics.** v0.3.0 raised an
|
|
290
|
+
`AdapterError` pointing at the v0.3.x roadmap for any non-builtin
|
|
291
|
+
topic ; v0.4.0 Phase 1 returns best-effort decoded samples. The
|
|
292
|
+
payload may carry three reserved keys:
|
|
293
|
+
- `_decode_status`: `"full"` (every IDL field decoded) /
|
|
294
|
+
`"partial"` (some fields decoded, others opaque) /
|
|
295
|
+
`"raw"` (binding could not resolve the dynamic XTypes — bytes
|
|
296
|
+
preserved as hex).
|
|
297
|
+
- `_decode_note`: short diagnostic explaining the non-`full` status.
|
|
298
|
+
- `_raw_bytes_hex`: hex-encoded serialized payload preview (capped
|
|
299
|
+
at 4096 hex chars ; `_raw_bytes_truncated=True` flags clipping).
|
|
300
|
+
- **`adapters/common/xtypes.py`** — adapter-agnostic helpers
|
|
301
|
+
(`annotate_full`, `annotate_partial`, `annotate_raw`) so Cyclone and
|
|
302
|
+
Fast DDS produce identical wire output regardless of binding
|
|
303
|
+
capabilities. Pure logic, testable without any SDK.
|
|
304
|
+
- **Mock fixtures**: two new user-topic exemplars
|
|
305
|
+
(`/dds/ddsforge/example` returns `_decode_status="full"` ;
|
|
306
|
+
`/dds/ddsforge/opaque` returns `_decode_status="raw"`) so the
|
|
307
|
+
payload shape can be exercised end-to-end without a DDS bus.
|
|
308
|
+
|
|
309
|
+
#### Changed (continued — sub-milestone 1.3)
|
|
310
|
+
|
|
311
|
+
- **`CycloneDdsAdapter.peek_dds_samples`** — user topics now go
|
|
312
|
+
through `_peek_user_topic`. The path probes builtin DCPS
|
|
313
|
+
Subscription / Publication readers to confirm the topic is on the
|
|
314
|
+
bus (raises `AdapterError` if not), then attempts
|
|
315
|
+
`cyclonedds.dynamic` dynamic decode ; on failure (most cases at
|
|
316
|
+
v0.4.0 Phase 1 — full decode lands in a Phase 1+ patch) returns a
|
|
317
|
+
single annotated raw-bytes sample with `_decode_status="raw"`.
|
|
318
|
+
- **`FastDdsAdapter.peek_dds_samples`** — symmetric shape via
|
|
319
|
+
`_peek_user_topic`. Fast DDS 2.6.x dynamic XTypes is partial, so
|
|
320
|
+
the raw-bytes fallback is the common path ; the wire shape is
|
|
321
|
+
identical to Cyclone's. Same `AdapterError` on unknown topic.
|
|
322
|
+
- **`peek_dds_samples` tool description** updated to remove the stale
|
|
323
|
+
`"v0.2.0 stub"` wording and document the user-topic decoding story
|
|
324
|
+
(full / partial / raw status, `_raw_bytes_hex` shape, binding
|
|
325
|
+
caveats per backend).
|
|
326
|
+
|
|
327
|
+
### Sprint v0.4.0 — Phase 1.5 (auto-detect + OSS expansion + Pro framing)
|
|
328
|
+
|
|
329
|
+
> Same `[Unreleased]` line. Phase 1.5 is the bridge between Phase 1 (DDS
|
|
330
|
+
> observability core) and Phase 2 (Pro tier launch). Branch
|
|
331
|
+
> `feat/v0.4.0-phase15-auto-detect-and-oss-expansion`. No version bump.
|
|
332
|
+
|
|
333
|
+
#### Added (sub-milestone 1.5.1 — auto-detect + XTypes push)
|
|
334
|
+
|
|
335
|
+
- **8-vendor DDS auto-detect chain** in `Settings.effective_dds_backend`.
|
|
336
|
+
Priority order: `rti > opensplice > coredx > intercom (Pro) > opendds
|
|
337
|
+
> fast > cyclone > dust (OSS) > mock`. The chain probes each vendor's
|
|
338
|
+
Python module via `importlib.util.find_spec` and returns the first
|
|
339
|
+
hit. Pro vendors are probed against `topicforge_pro.adapters.<vendor>`
|
|
340
|
+
rather than the upstream SDK directly — the OSS core never imports a
|
|
341
|
+
commercial vendor binding.
|
|
342
|
+
- **`DdsBackend` / `ResolvedDdsBackend` Literals widened** with 5 new
|
|
343
|
+
vendor values: `opensplice`, `coredx`, `intercom`, `opendds`, `dust`.
|
|
344
|
+
- **`HealthReport.dds_backend` Literal widened** symmetrically. Soft-
|
|
345
|
+
breaking on the producer side ; strict JSON-schema clients pinned to
|
|
346
|
+
v0.3.0 will reject the new values unless their schema regenerates.
|
|
347
|
+
- **`AdapterName` Literal widened** with the 5 new vendor tags and 6
|
|
348
|
+
new composite tags (`ros2_cli+rti`, `ros2_cli+opensplice`, etc.).
|
|
349
|
+
- **Cyclone XTypes pipeline**. `_try_dynamic_decode_cyclone` no longer
|
|
350
|
+
always returns None — it probes `cyclonedds.dynamic` entry points,
|
|
351
|
+
resolves a type id via `DCPSPublication`, builds a typed reader, and
|
|
352
|
+
decodes samples field-by-field with per-construct granularity.
|
|
353
|
+
Fallback to `annotate_raw` when any step fails. Real-bus validation
|
|
354
|
+
awaits user feedback ; the v0.4.0 Phase 1 plumbing is now a
|
|
355
|
+
best-effort path rather than always-fallback.
|
|
356
|
+
- **Fast DDS `TypeObjectFactory` probe** in `_try_dynamic_decode_fast`.
|
|
357
|
+
The Fast DDS 2.6.x dynamic XTypes Python surface is incomplete, so
|
|
358
|
+
the probe currently returns None and the raw-bytes fallback fires ;
|
|
359
|
+
the structural change makes the v0.5 decode patch a small follow-up.
|
|
360
|
+
|
|
361
|
+
#### Added (sub-milestone 1.5.2 — OSS adapter expansion)
|
|
362
|
+
|
|
363
|
+
- **`OpenDdsAdapter`** (`adapters/dds_opendds/`) — stub. Probes
|
|
364
|
+
`pyopendds` via `find_spec` ; `is_available()` reports False when the
|
|
365
|
+
binding is absent (always, in 2026-05-14, since the package is not on
|
|
366
|
+
PyPI). All 8 protocol methods raise `AdapterError(_OPENDDS_ROADMAP_MSG)`.
|
|
367
|
+
- **`DustDdsAdapter`** (`adapters/dds_dust/`) — even thinner stub.
|
|
368
|
+
`is_available()` always False ; Dust DDS is Rust-native with no
|
|
369
|
+
maintained Python binding.
|
|
370
|
+
- **`[dds-opendds]` and `[dds-dust]` pyproject extras** as placeholder
|
|
371
|
+
pins (`pyopendds>=0.1`, `dust-dds-python>=0.1`). Neither package is
|
|
372
|
+
on PyPI today ; the extras anchor the auto-detect probe and will
|
|
373
|
+
resolve cleanly when upstream releases.
|
|
374
|
+
- **`[dds-all-oss]` union extra** — `topicforge[dds] + dds-opendds +
|
|
375
|
+
dds-dust` for users explicitly opting into the stubs.
|
|
376
|
+
- **New pytest markers** `requires_opendds`, `requires_dust` — auto-skip
|
|
377
|
+
when bindings are absent (same convention as `requires_cyclonedds`).
|
|
378
|
+
|
|
379
|
+
#### Added (sub-milestone 1.5.3 — pro/ scaffold + docs)
|
|
380
|
+
|
|
381
|
+
- **`tests/test_pro_hook.py`** — exercises `_try_register_pro` with a
|
|
382
|
+
fake `topicforge_pro` module injected via `sys.modules`. Three test
|
|
383
|
+
cases: package absent → False, package present + register succeeds →
|
|
384
|
+
True + side-effect captured, register raises → False + error logged.
|
|
385
|
+
Locks in the OSS-side contract that the Pro plugin must implement.
|
|
386
|
+
- **`docs/pro.md` rewritten** with the corrected tier framing: OSS =
|
|
387
|
+
community DDS adapters (Cyclone, Fast — OpenDDS / Dust stubs) + base
|
|
388
|
+
ROS2 introspection ; Pro = commercial DDS adapters (RTI Connext,
|
|
389
|
+
OpenSplice [legacy], CoreDX, InterCOM) + URDF / Bag Anomaly /
|
|
390
|
+
Multi-bag Diff diagnostics. Pricing terms preserved at $12/$19.
|
|
391
|
+
|
|
392
|
+
#### Changed (sub-milestone 1.5.3)
|
|
393
|
+
|
|
394
|
+
- **README.md install section** enriched. Documents the 8-vendor auto-
|
|
395
|
+
detect chain and the OSS/Pro tier split. Removed the stale "v0.3.0
|
|
396
|
+
limitation — single adapter at a time" — Phase 1 shipped the
|
|
397
|
+
composite adapter ; the README now describes the actual behavior.
|
|
398
|
+
|
|
399
|
+
#### Notes
|
|
400
|
+
|
|
401
|
+
- **Pro tier scaffold lives in `pro/` (gitignored).** The Phase 1.5
|
|
402
|
+
branch ships RtiConnextAdapter + OpenSplice stub + license skeleton
|
|
403
|
+
+ register.py in the working tree under `pro/`, but `.gitignore`
|
|
404
|
+
excludes the folder entirely — none of those files are committed.
|
|
405
|
+
The maintainer copies them to a private backup repo before launching
|
|
406
|
+
the `topicforge-pro` PyPI package.
|
|
407
|
+
- **Backward compatibility**: zero v0.3.0 + Phase 1 test regressions.
|
|
408
|
+
`TOPICFORGE_DDS_BACKEND=mock | cyclone | fast | rti | auto` continue
|
|
409
|
+
to resolve as before ; the 5 new vendor values are additive.
|
|
410
|
+
- **No 10th MCP tool.** Phase 1.5 is structural / docs only — the
|
|
411
|
+
9-tool surface (8 v0.3.0 + `participant_events` from Phase 1) is
|
|
412
|
+
preserved exactly.
|
|
413
|
+
- **Pyopendds and dust-dds-python pins are placeholders.** A user
|
|
414
|
+
running `pip install topicforge[dds-opendds]` today will see an
|
|
415
|
+
install failure ; this is expected and the extras exist to anchor
|
|
416
|
+
the auto-detect framework for the day the upstream packages ship.
|
|
417
|
+
|
|
418
|
+
## [0.3.0] - 2026-05-14
|
|
419
|
+
|
|
420
|
+
### Strategic
|
|
421
|
+
|
|
422
|
+
- **OMG DDS-RTPS multi-vendor positioning.** TopicForge is now framed as a read-only DDS-RTPS observer that joins the bus via one of two OSS Python participants — Eclipse CycloneDDS or eProsima Fast DDS — and observes every conformant vendor on the domain (RTI Connext, OpenDDS, CoreDX, Dust DDS in Rust, etc.) regardless of host language. See `docs/dds-interop-matrix.md` for the canonical statement and `docs/projet-file/references/omg-dds-interop-2025-05-08.xlsx` for the OMG May 2025 interop reference. The earlier v0.2.0/v0.3.0 phasing (Cyclone-only at v0.3.0, RTI at v0.3.0+) is collapsed: multi-vendor OSS lands together at v0.3.0 ; RTI Pro defers to v0.4.0+.
|
|
423
|
+
|
|
424
|
+
### Added
|
|
425
|
+
|
|
426
|
+
- **Real `CycloneDdsAdapter`** — replaces the v0.2.0 stub with actual CycloneDDS discovery via `cyclonedds.builtin.BuiltinDataReader` on the DCPS participant/subscription/publication builtin topics. QoS extracted via `Policy.*` class-name introspection. `take_iter(timeout=...)` for bounded discovery. Lazy-imported via `services.factory`.
|
|
427
|
+
- **`FastDdsAdapter`** (`adapters/dds_fast/`) — new parallel OSS adapter built on the `fastdds` Python bindings (BSD-licensed, eProsima). Duck-typed listener subclass aggregates discovery callbacks under an RLock. Bounded `discovery_wait_ms=1500` warm-up after participant creation. `close()` releases the participant via `factory.delete_participant`.
|
|
428
|
+
- **`adapters/common/dds_helpers.py`** — vendor-neutral helpers: `canonicalize_vendor_id` (OMG vendor_id → canonical tag), `format_guid` (16-byte GUID → `xxxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx` canonical text), `DDS_ONLY_ERROR_MSG` (shared remediation message). Pure functions, no DDS dependency.
|
|
429
|
+
- **Pyproject extras refactor**: `[dds-cyclone]` (Cyclone only), `[dds-fast]` (Fast only), `[dds]` (both — union of v0.2.0 `[dds]` behavior plus fastdds).
|
|
430
|
+
- **`TOPICFORGE_DDS_BACKEND=fast`** — new accepted value alongside `mock | cyclone | rti | auto`.
|
|
431
|
+
- **3rd mock participant** with `vendor="fast"` exercises the multi-vendor positioning in mock mode.
|
|
432
|
+
- **New pytest marker** `requires_fastdds` — auto-skips without the binding.
|
|
433
|
+
- **35+ new tests**: `tests/test_dds_helpers.py`, `tests/test_fast_adapter.py`, `tests/test_dds_cross_vendor.py` (parametrized on both adapters), 6 analyzer edge cases in `tests/test_qos_analyzer.py`, 4 new health tests for DDS field population.
|
|
434
|
+
|
|
435
|
+
### Changed
|
|
436
|
+
|
|
437
|
+
- **`ParticipantInfo.vendor` Literal widened** to include `"fast"`. **Strict JSON-schema clients pinned to v0.2.0 will reject `vendor:fast` unless their schema is regenerated.** Standard MCP clients reading tool descriptions dynamically are unaffected.
|
|
438
|
+
- **`HealthReport.dds_backend` Literal widened** to include `"fast"`. Same soft-breaking caveat.
|
|
439
|
+
- **`AdapterName` Literal widened** to include `"fast"` (internal type — no wire impact).
|
|
440
|
+
- **`DdsBackend` / `ResolvedDdsBackend`** widened to include `"fast"`.
|
|
441
|
+
- **`Settings.effective_dds_backend` auto resolution** now prefers Fast DDS > Cyclone DDS > Mock (was Cyclone > Mock in v0.2.0). v0.2.0 users with only `cyclonedds` installed see no change — Fast is unimportable on their host. Users with both SDKs installed will see Fast selected. Reflects the OMG May 2025 interop matrix.
|
|
442
|
+
- **`HealthService.report()`** now populates `dds_backend`, `dds_domain_id`, `middleware_available` — previously returned schema defaults regardless of configuration (v0.2.0 latent bug). `middleware_available` is checked via `importlib.util.find_spec` on the active backend's Python module.
|
|
443
|
+
- **`Ros2CliAdapter._DDS_MODULE_INACTIVE_MSG`** updated to mention both `pip install topicforge[dds-cyclone]` and `pip install topicforge[dds-fast]` remediation paths.
|
|
444
|
+
- **`Inspector` DDS topic validator relaxed** — `detect_qos_mismatches` and `peek_dds_samples` now accept DDS-native topic names (no leading `/` required, `::` separators allowed) via a new `_validate_topic_name_dds`. The strict ROS2 validator stays in place for the 5 ROS2 graph methods. Resolves audit-2026-05-14 "Refactor opportunities" #5.
|
|
445
|
+
|
|
446
|
+
### Removed
|
|
447
|
+
|
|
448
|
+
- **`CycloneDdsAdapter` v0.2.0 stub** — `_NOT_IMPLEMENTED_MSG` and the corresponding `test_dds_surface_raises_stub_error_in_v020` test removed. The 3 DDS methods now serve real results when cyclonedds is installed.
|
|
449
|
+
|
|
450
|
+
### Notes
|
|
451
|
+
|
|
452
|
+
- **OMG-DDS-RTPS interoperability** is the protocol guarantee that makes multi-vendor observation work — see `docs/dds-interop-matrix.md` and `docs/projet-file/references/omg-dds-interop-2025-05-08.xlsx`.
|
|
453
|
+
- **v0.3.0 `peek_dds_samples` limitation** — full-fidelity on the 4 builtin DCPS topics (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`) ; arbitrary user topics raise an `AdapterError` with a v0.3.x roadmap pointer (XTypes/IDL discovery is the missing piece, both for Cyclone via `cyclonedds.dynamic.get_types_for_typeid` and for Fast DDS via XTypes remote type lookup).
|
|
454
|
+
- **`pip install topicforge[dds]` in v0.3.0** now pulls BOTH `cyclonedds` and `fastdds` (was Cyclone only in v0.2.0). Use `[dds-cyclone]` or `[dds-fast]` for single-vendor installs. See `docs/MIGRATION_v0.2_to_v0.3.md`.
|
|
455
|
+
- **Fast DDS pin**: `fastdds>=2.6.1,<3` — Fast DDS 3.x binding wheels for Python 3.11+ on Windows / Linux are not yet stable. Bump when upstream cuts stable 3.x wheels.
|
|
456
|
+
- **No code change to the 5 ROS2 tools** — `health_check`, `list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag` behave identically to v0.2.0. The `mode_effective` wire contract is unchanged ; `health_check` now populates DDS fields correctly.
|
|
457
|
+
- **Full migration guide**: `docs/MIGRATION_v0.2_to_v0.3.md`.
|
|
458
|
+
|
|
459
|
+
## [0.2.0] - 2026-05-14
|
|
460
|
+
|
|
461
|
+
### Strategic
|
|
462
|
+
|
|
463
|
+
- **Mono-MCP pivot (2026-05-14).** The 3-to-5-MCP pack draft is collapsed into a 2-product strategy: TopicForge umbrella (this product — covers ROS2 today and grows a DDS observability module starting with v0.2.0), and **DatasetForge** (Vision Dataset Inspector, the standalone second product). The previously-planned standalone DDS-MCP product is cancelled — its spec is reframed as the TopicForge DDS module spec at `docs/projet-file/mcp-02-spec.md`. Motif: solo-maintenance cost of two parallel repos was the binding constraint, and ROS2 / DDS are the same problem shape (typed pub/sub graph introspection) under the same `MiddlewareAdapter` superset.
|
|
464
|
+
|
|
465
|
+
### Added
|
|
466
|
+
|
|
467
|
+
- **DDS module — 3 new MCP tools.** `list_participants(domain_id)`, `detect_qos_mismatches(topic)`, `peek_dds_samples(topic, count)`. All read-only ; surface DDS-layer introspection distinct from the ROS2 graph tools. `peek_dds_samples` is deliberately separate from `sample_messages` — different layer, different semantics, distinct tool description so an LLM picks the right one in a mixed setup.
|
|
468
|
+
- **`MiddlewareAdapter` protocol** in `adapters/base.py` — superset of the historical `RosAdapter`. Covers both ROS2 graph methods and the new DDS methods under one contract. `RosAdapter` retained as a backward-compat alias (`RosAdapter = MiddlewareAdapter`).
|
|
469
|
+
- **`CycloneDdsAdapter`** (`adapters/dds_cyclone/`) — lazy-imported only when `TOPICFORGE_DDS_BACKEND=cyclone` and the optional `cyclonedds` extras are installed (`pip install topicforge[dds]`). **v0.2.0 ships a protocol-compliant stub**: the lazy import, `is_available()`, and routing all work ; the 3 DDS methods raise `AdapterError` with a v0.2.x roadmap pointer. The real CycloneDDS discovery (builtin topics, QoS pair extraction, typed reader for samples) lands in a v0.2.x patch. The mock backend (`TOPICFORGE_DDS_BACKEND=mock`, the default) exposes a working DDS surface against deterministic fixtures in the meantime.
|
|
470
|
+
- **3 new Pydantic schemas**: `QosProfile` (Reliability / Durability / History / Deadline at MVP), `ParticipantInfo` (GUID, vendor, hostname, domain_id), `MismatchReport` (incompatible_policies + severity). All frozen, `extra="forbid"`.
|
|
471
|
+
- **Pure analyzer** `adapters/common/qos_analyzer.detect_mismatches` — module-level pure function, testable against synthesized QoS pairs without any DDS middleware installed.
|
|
472
|
+
- **Environment variables**:
|
|
473
|
+
- `TOPICFORGE_DDS_BACKEND` — `mock | cyclone | rti | auto`, default `mock`. The DDS module is opt-in ; existing ROS2-only setups behave unchanged.
|
|
474
|
+
- `TOPICFORGE_DDS_DOMAIN_ID` — DDS domain id observed (0..232), default `0`.
|
|
475
|
+
- **Mock fixtures enriched**: 2 deterministic DDS participants, two-topic scenario (`/dds/well_matched` and `/dds/qos_mismatch`) exercising `detect_qos_mismatches` end-to-end.
|
|
476
|
+
- **`pyproject.toml` extras**: `[dds]` pulls `cyclonedds>=0.10` ; `[all]` aliases `[dds]`. `pip install topicforge` keeps the core + mock only (zero install impact on ROS2-only users).
|
|
477
|
+
|
|
478
|
+
### Changed
|
|
479
|
+
|
|
480
|
+
- **`TopicInfo` schema soft-breaking.** Three additive optional fields (`reader_count: int | None`, `writer_count: int | None`, `qos_profile: QosProfile | None`) — all default `None`. Producer side: code constructing `TopicInfo` directly is unaffected (defaults compile). **Strict MCP clients that validated v0.1.x responses against the `TopicInfo` schema with `additionalProperties: false` will reject v0.2.0 responses unless their schema is regenerated. Standard MCP clients that read tool descriptions dynamically are unaffected.**
|
|
481
|
+
- **`HealthReport` schema soft-breaking**, same shape. Three additive optional fields (`dds_backend`, `dds_domain_id`, `middleware_available`) with safe defaults (`"none"`, `None`, `False`).
|
|
482
|
+
- **`RosAdapter` renamed to `MiddlewareAdapter`** in `adapters/base.py`. The old name remains as an alias (`RosAdapter = MiddlewareAdapter`) ; existing imports `from topicforge.adapters import RosAdapter` still type-check. The `Ros2CliAdapter.name` value moves from `"live"` to `"ros2_cli"` — internal tag, separate from the MCP-wire `mode_effective` field which keeps its `Literal["mock", "live"]` contract.
|
|
483
|
+
- **`Settings`** gains `dds_backend` and `dds_domain_id` fields with safe defaults (`"mock"`, `0`). Existing `Settings(...)` constructors are unaffected.
|
|
484
|
+
- **`Ros2CliAdapter` DDS methods raise `AdapterError`** with a clear remediation path (`pip install topicforge[dds]` + `TOPICFORGE_DDS_BACKEND=cyclone`). This is the v0.2.0 MVP limitation D6 (single-adapter-at-a-time) ; a composite adapter that delegates per-tool is a v0.2.x roadmap item.
|
|
485
|
+
|
|
486
|
+
### Internal
|
|
487
|
+
|
|
488
|
+
- `parse_topic_info` and `parse_bag_info` parsers : `mode_effective` kwarg typed as `EffectiveMode` (`Literal["mock", "live"]`) rather than the broader `AdapterName`, cleanly separating the wire-facing mode from the implementation tag.
|
|
489
|
+
- New pytest marker `requires_cyclonedds` for tests that need the SDK. Auto-skips otherwise via `pytest.importorskip`.
|
|
490
|
+
|
|
491
|
+
### Notes
|
|
492
|
+
|
|
493
|
+
- **`cyclonedds` is optional.** Default installs (`pip install topicforge`) are unchanged from v0.1.2 in dependency footprint. Only `pip install topicforge[dds]` pulls the bindings (`cyclonedds>=0.10`).
|
|
494
|
+
- **No code change to the 5 ROS2 tools** — `health_check`, `list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag` behave identically to v0.1.2. The wire contract (`mode_effective: Literal["mock", "live"]`) is unchanged.
|
|
495
|
+
- **v0.2.0 MVP limitation**: single adapter at a time. Users select ROS2 introspection (default) or DDS observability via `TOPICFORGE_DDS_BACKEND=cyclone`, not both simultaneously. The unselected half raises `AdapterError` with a remediation pointer. A composite adapter delegating per-tool category is a v0.2.x roadmap item.
|
|
496
|
+
|
|
497
|
+
## [0.1.2] - 2026-05-13
|
|
498
|
+
|
|
499
|
+
### Fixed
|
|
500
|
+
|
|
501
|
+
- `sample_messages` now returns real publish-time timestamps in live mode for `Header`-stamped messages. The live adapter previously shelled out to `ros2 topic echo --once`, which does not emit timestamps, so `MessageSample.timestamp_ns` was always `0`. The invocation is now `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec` and `header.stamp.nanosec` as the first two columns for any `Header`-stamped message; the new `parse_csv_echo` parser reconstructs `timestamp_ns = sec * 1_000_000_000 + nanosec` and strips those two columns out of the payload. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) still return `timestamp_ns=0` — they carry no embedded timestamp. Surfacing the rmw **receive** timestamp (rather than the publish-time `header.stamp`) for arbitrary message types remains a roadmap item tied to the future `rclpy`-backed adapter.
|
|
502
|
+
|
|
503
|
+
### Added
|
|
504
|
+
|
|
505
|
+
- **`mode_effective` on every tool response (schema, soft-breaking additive).** `TopicInfo`, `SampleResult`, and `BagAnalysis` now carry a required `mode_effective: Literal["mock", "live"]` field. A new `effective_mode` property on the `RosAdapter` protocol is the single source of truth; `Ros2CliAdapter` returns `"live"`, `MockAdapter` returns `"mock"`, services thread it through at result construction time. **Producer side**: Python code constructing these models directly must now supply `mode_effective` — models are `frozen=True, extra="forbid"` with no default. **Client side (over MCP)**: additive — an MCP client consuming JSON sees one extra key per response and is unaffected unless it strictly validates against the v0.1.1 schema with a no-extra-keys assumption.
|
|
506
|
+
- **DDS-MCP spec** (`docs/projet-file/mcp-02-spec.md`). Strategic draft for MCP 02 at the time: safety-first read-only DDS observability across middleware vendors (CycloneDDS OSS, RTI Connext Pro tier). Five tools, `MiddlewareAdapter` protocol, mock + cyclone + rti + auto modes. Reviewer notes appended (2026-05-13): wrong cross-reference in §11 flagged. (Reframed the next day as a TopicForge module after the mono-MCP pivot — see [0.2.0] Strategic section.)
|
|
507
|
+
- **DatasetForge spec** (`docs/projet-file/mcp-03-spec.md`). Vision Dataset Inspector spec, re-slotted to MCP 03 after competitive-landscape audit that surfaced zero non-ROS DDS-MCP projects and made a standalone DDS-MCP the stronger MCP 02 candidate. Reviewer notes appended (2026-05-13): contradictory §11 phrasing and two implicitly-resolved open questions flagged.
|
|
508
|
+
|
|
509
|
+
### Changed
|
|
510
|
+
|
|
511
|
+
- **Safety-first read-only repositioning.** README and `docs/product-plan.md §1` now lead with "read-only by architecture, not by configuration" as the primary identity. Pack candidate list updated: MCP 02 reframed to a non-ROS DDS observability MCP (later folded into TopicForge itself by the 2026-05-14 multi-vendor reframe — see v0.3.0 entry); DatasetForge slides to MCP 03. Strategic context in `docs/product-plan.md §4` and §8 (DDS-complete horizon).
|
|
512
|
+
- **Internal API.** `Inspector.sample_messages` now returns a `SampleResult` envelope (previously a `list[MessageSample]`). The MCP-facing tool handler is reduced to a thin pass-through. No effect on the tool's wire-level response shape (handlers already wrapped the list into `SampleResult`), but flagged here for anyone importing `Inspector` directly outside this repo.
|
|
513
|
+
|
|
514
|
+
### Internal
|
|
515
|
+
|
|
516
|
+
- Docstring fix in `parse_csv_echo`: the example output now shows post-strip payload keys as `col_0`, `col_1` (the parser re-indexes from `col_0` after dropping the two timestamp columns), matching the existing test in `tests/test_live_adapter_parse.py`.
|
|
517
|
+
|
|
518
|
+
## [0.1.1] - 2026-05-13
|
|
519
|
+
|
|
520
|
+
### Added
|
|
521
|
+
|
|
522
|
+
- **Opt-in anonymous usage telemetry** behind `TOPICFORGE_TELEMETRY=on` (default: off). When enabled, each MCP tool call emits a single event with six fields only: `tool_name`, `latency_ms`, `mode`, `version`, `session_id` (random UUID per process, never persisted), and `success`. No topic names, message bodies, bag paths, hostnames, or environment data ever leave the process. See the README "Telemetry" section for the full payload contract and opt-out instructions.
|
|
523
|
+
- `src/topicforge/telemetry/` module with `TelemetryClient`, `TelemetryEvent`, and an `instrument()` decorator that wraps tool handlers with timing + emit. When telemetry is off, `instrument()` is the identity function — zero overhead and zero possibility of a network call in the OFF code path.
|
|
524
|
+
- Pluggable `Transport` callable; v0.1.1 ships a structured-log transport. A future S3-backed HTTP endpoint will plug in without touching tool handlers.
|
|
525
|
+
- 29 telemetry tests covering: default-off behaviour, env var parsing (`on`/`1`/`true`/`yes`/`enabled` vs anything else), payload shape and key allowlist, payload privacy (user input never leaks), session id stability and per-process uniqueness, transport-exception isolation, decorator signature preservation, and end-to-end verification that the OFF code path never invokes the transport.
|
|
526
|
+
|
|
527
|
+
### Changed
|
|
528
|
+
|
|
529
|
+
- `Settings` gained a `telemetry_enabled: bool` field.
|
|
530
|
+
- `build_app(...)` accepts optional `telemetry` and `telemetry_transport` parameters for test injection.
|
|
531
|
+
- `register_tools(...)` now takes a `TelemetryClient`.
|
|
532
|
+
- `.env.example` documents `TOPICFORGE_TELEMETRY`.
|
|
533
|
+
- README adds a `Telemetry` section and updates the Security model note to reflect opt-in telemetry availability.
|
|
534
|
+
|
|
535
|
+
## [0.1.0] - 2026-05-12
|
|
536
|
+
|
|
537
|
+
Initial MVP release of TopicForge — ROS Topic Inspector & Bag Analyzer MCP server.
|
|
538
|
+
|
|
539
|
+
### Added
|
|
540
|
+
|
|
541
|
+
- Five read-only MCP tools exposed over FastMCP: `health_check`, `list_topics`, `get_topic_info`, `sample_messages`, and `analyze_bag`.
|
|
542
|
+
- `RosAdapter` protocol in `adapters/base.py` defining the contract every backend implements.
|
|
543
|
+
- Mock adapter (`adapters/ros2_mock/`) with deterministic fixtures modeling a small differential mobile robot equipped with a LIDAR and an RGB camera.
|
|
544
|
+
- Live adapter (`adapters/ros2_live/`) built on subprocess wrappers around the `ros2` CLI, with pure module-level parsers tested independently of any ROS2 install.
|
|
545
|
+
- Three runtime modes selectable via `TOPICFORGE_MODE`: `mock`, `live`, and `auto`. The `auto` resolution lives in `Settings.effective_mode`; the live-to-mock fallback when the adapter cannot start lives in `services/factory.py`.
|
|
546
|
+
- Windows-first cross-platform support: executable resolution via `shutil.which` (handles `ros2.cmd` / `ros2.bat` shims), `subprocess.run` called with absolute paths and never `shell=True`, all filesystem paths via `pathlib.Path`.
|
|
547
|
+
- Pydantic v2 schemas in `models/` configured with `extra="forbid"` and `frozen=True`, returned as the structured payload of every tool.
|
|
548
|
+
- Pytest suite that runs entirely without a ROS2 environment, covering services, mock adapter, and live-adapter parsers.
|
|
549
|
+
- Build, lint, and tooling configuration: Python 3.11+, `mcp >= 1.0.0` (FastMCP), `pydantic >= 2.6`, pytest, ruff, hatchling.
|
|
550
|
+
- Licensed under the MIT License.
|
|
551
|
+
|
|
552
|
+
### Notes
|
|
553
|
+
|
|
554
|
+
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
555
|
+
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
556
|
+
|
|
557
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.3.0...HEAD
|
|
558
|
+
[0.3.0]: https://github.com/yaniswav/TopicForge/compare/v0.2.0...v0.3.0
|
|
559
|
+
[0.2.0]: https://github.com/yaniswav/TopicForge/compare/v0.1.2...v0.2.0
|
|
560
|
+
[0.1.2]: https://github.com/yaniswav/TopicForge/compare/v0.1.1...v0.1.2
|
|
561
|
+
[0.1.1]: https://github.com/yaniswav/TopicForge/compare/v0.1.0...v0.1.1
|
|
562
|
+
[0.1.0]: https://github.com/yaniswav/TopicForge/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topicforge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: ROS Topic Inspector & Bag Analyzer MCP server for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/yaniswav/TopicForge
|
|
6
6
|
Project-URL: Repository, https://github.com/yaniswav/TopicForge
|
|
@@ -43,13 +43,24 @@ Requires-Dist: pydantic>=2.6
|
|
|
43
43
|
Provides-Extra: all
|
|
44
44
|
Requires-Dist: cyclonedds>=0.10; extra == 'all'
|
|
45
45
|
Requires-Dist: fastdds<3,>=2.6.1; extra == 'all'
|
|
46
|
+
Provides-Extra: bags
|
|
47
|
+
Requires-Dist: rosbags>=0.9; extra == 'bags'
|
|
46
48
|
Provides-Extra: dds
|
|
47
49
|
Requires-Dist: cyclonedds>=0.10; extra == 'dds'
|
|
48
50
|
Requires-Dist: fastdds<3,>=2.6.1; extra == 'dds'
|
|
51
|
+
Provides-Extra: dds-all-oss
|
|
52
|
+
Requires-Dist: cyclonedds>=0.10; extra == 'dds-all-oss'
|
|
53
|
+
Requires-Dist: dust-dds-python>=0.1; extra == 'dds-all-oss'
|
|
54
|
+
Requires-Dist: fastdds<3,>=2.6.1; extra == 'dds-all-oss'
|
|
55
|
+
Requires-Dist: pyopendds>=0.1; extra == 'dds-all-oss'
|
|
49
56
|
Provides-Extra: dds-cyclone
|
|
50
57
|
Requires-Dist: cyclonedds>=0.10; extra == 'dds-cyclone'
|
|
58
|
+
Provides-Extra: dds-dust
|
|
59
|
+
Requires-Dist: dust-dds-python>=0.1; extra == 'dds-dust'
|
|
51
60
|
Provides-Extra: dds-fast
|
|
52
61
|
Requires-Dist: fastdds<3,>=2.6.1; extra == 'dds-fast'
|
|
62
|
+
Provides-Extra: dds-opendds
|
|
63
|
+
Requires-Dist: pyopendds>=0.1; extra == 'dds-opendds'
|
|
53
64
|
Provides-Extra: dev
|
|
54
65
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
55
66
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
@@ -200,7 +211,7 @@ Beyond ROS2 graph introspection, TopicForge observes the raw DDS bus directly vi
|
|
|
200
211
|
|
|
201
212
|
Useful for non-ROS DDS stacks (defense, aerospace, automotive AUTOSAR Adaptive, industrial integration) and for diagnosing why a ROS2 subscriber isn't receiving when the graph says it should. Same safety-first contract : read-only by **architecture** — the `MiddlewareAdapter` protocol does not expose a write method on any backend.
|
|
202
213
|
|
|
203
|
-
Install one or both OSS backends :
|
|
214
|
+
Install one or both OSS backends — the v0.4.0 auto-detect framework picks whichever you actually installed:
|
|
204
215
|
|
|
205
216
|
```bash
|
|
206
217
|
# Single vendor — install only what you need
|
|
@@ -209,16 +220,25 @@ pip install topicforge[dds-fast] # eProsima Fast DDS only
|
|
|
209
220
|
pip install topicforge[dds] # both OSS backends (union)
|
|
210
221
|
```
|
|
211
222
|
|
|
212
|
-
Then select a backend :
|
|
223
|
+
Then select a backend (or let auto-detect pick):
|
|
213
224
|
|
|
214
225
|
```bash
|
|
215
226
|
TOPICFORGE_MODE=live TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
216
227
|
# or:
|
|
217
228
|
TOPICFORGE_MODE=live TOPICFORGE_DDS_BACKEND=fast python -m topicforge
|
|
218
|
-
# or, auto-select
|
|
229
|
+
# or, auto-select across the 8-vendor priority chain:
|
|
230
|
+
# RTI > OpenSplice > CoreDX > InterCOM (Pro tier, if installed)
|
|
231
|
+
# > OpenDDS > Fast > Cyclone > Dust > Mock (OSS fallback)
|
|
219
232
|
TOPICFORGE_MODE=live TOPICFORGE_DDS_BACKEND=auto python -m topicforge
|
|
220
233
|
```
|
|
221
234
|
|
|
235
|
+
**OSS vs Pro tier (v0.4.0+).** The OSS install above covers the community
|
|
236
|
+
DDS adapters (Cyclone, Fast — plus OpenDDS / Dust stubs awaiting upstream
|
|
237
|
+
Python bindings). The commercial DDS adapters (RTI Connext, OpenSplice,
|
|
238
|
+
CoreDX, InterCOM) ship under the optional `topicforge-pro` package with
|
|
239
|
+
BYO vendor license. See [`docs/pro.md`](docs/pro.md) for the early-access
|
|
240
|
+
slot and pricing terms ; nothing is collected today.
|
|
241
|
+
|
|
222
242
|
Three new MCP tools (in addition to the five ROS2 tools above) :
|
|
223
243
|
|
|
224
244
|
| Tool | Purpose |
|
|
@@ -227,7 +247,7 @@ Three new MCP tools (in addition to the five ROS2 tools above) :
|
|
|
227
247
|
| `detect_qos_mismatches` | Reader/writer QoS incompatibilities preventing communication on a topic |
|
|
228
248
|
| `peek_dds_samples` | Recent samples on a raw DDS topic (distinct from `sample_messages` on ROS2 graph)|
|
|
229
249
|
|
|
230
|
-
**v0.
|
|
250
|
+
**Composite adapter (v0.4.0 Phase 1+).** When `TOPICFORGE_MODE=live` is paired with a DDS backend (`cyclone`, `fast`, …), TopicForge instantiates **both** a ROS2 CLI adapter and the chosen DDS adapter and routes per-tool category — the 5 ROS2 tools hit the CLI, the 3 DDS tools (+ `participant_events` from Phase 1) hit the DDS backend. ROS2-only or DDS-only setups still work — the missing half is skipped and the present half serves what it can. The mock backend continues to expose all 9 tools against deterministic fixtures for local development.
|
|
231
251
|
|
|
232
252
|
**v0.3.0 limitation — `peek_dds_samples` scope.** Full-fidelity on the 4 builtin DCPS topics (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`) ; arbitrary user topics raise an `AdapterError` pointing at the v0.3.x XTypes/IDL roadmap. The other two DDS tools (`list_participants`, `detect_qos_mismatches`) work end-to-end on any user-topic deployment.
|
|
233
253
|
|