Skip to main content
Glama

TopicForge

PyPI version CI Python versions License: MIT Read-only

A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a ROS2 graph, recorded bag files and the DDS layer underneath ROS. The code has no write path: it cannot publish to the bus or command a robot, and there is no permission system to configure.

Every tool also declares itself read-only in the MCP protocol (readOnlyHint, destructiveHint=false, idempotentHint, with an honest openWorldHint), and a test fails if any tool lacks it. See SECURITY.md for the guarantee and its limits.

It gives the agent sixteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one note, units in the field names; see docs/CONTRACT.md), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why nav_planner gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see examples/02-debug-qos-mismatch.md). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.

For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with rmw_fastrtps_cpp) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See docs/dds-interop-matrix.md.

Quickstart

No ROS2 is needed; the mock adapter serves deterministic fixtures for a small differential robot (LIDAR + RGB camera). Python 3.10 to 3.13.

pip install topicforge
TOPICFORGE_MODE=mock python -m topicforge
# Windows PowerShell: $env:TOPICFORGE_MODE="mock"; python -m topicforge

The server speaks MCP over stdio and waits for a client, so wire it into one. For Claude Desktop, add to claude_desktop_config.json:

{
  "mcpServers": {
    "topicforge": {
      "command": "python",
      "args": ["-m", "topicforge"],
      "env": { "TOPICFORGE_MODE": "auto" }
    }
  }
}

Then ask it to list the topics or to analyze /tmp/demo.mcap. For Claude Code: claude mcp add topicforge -- topicforge. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), topicforge --transport streamable-http --port 8765 serves http://127.0.0.1:8765/mcp on loopback only; see docs/CLIENTS.md.

Other MCP clients. Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in docs/CLIENTS.md. One-click: Add to Cursor | Install in VS Code.

Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in docs/TESTING.md; recurring monitoring prompts and the privacy contract are in docs/TUTORIEL.md.

Related MCP server: ros2-mcp

Tools

Every response except health_check carries mode_effective ("live" or "mock"), so a caller can tell a real graph from fixtures, and a single note that explains anything unusual. health_check.contract_version is 2. The full reference, with every field, is generated in docs/TOOLS.md; upgrading from 0.6.x: docs/MIGRATION-0.7.md.

Tool

Purpose

health_check

Environment and mode introspection. Always succeeds; reports mode next to requested_mode

list_topics

Discover the ROS2 graph

get_topic_info

Message type, publisher/subscriber counts and QoS for one topic

list_nodes

ROS 2 nodes on the graph: names, namespaces, duplicate names (one ros2 node list call)

get_node_info

One node's publishers, subscribers, services, actions, parameters (secrets masked) and use_sim_time; a node that does not answer is reported, not an error

inspect_tf

The TF tree from /tf_static and /tf: roots, stale and two-parent frames, disconnected trees, sim-time-safe ages and rates, optional path between two frames

graph_diff

What changed since it last worked: snapshots the ROS 2 graph (or a bag) and diffs it with previous, a snap-N id or a bag: nodes, topics, types, writer QoS, counts, bag rates

sample_messages

Peek recent messages on a ROS2 topic (count capped at 50)

analyze_bag

Summarize a .mcap / .db3 recording or rosbag2_* directory (via ros2 bag info)

list_participants

DDS participants on the domain: vendor, name (EntityName QoS, Cyclone) and hostname

detect_qos_mismatches

Incompatible QoS pairs between DDS readers and writers

peek_dds_samples

Raw DDS samples; structured on the three builtin discovery topics, presence-only on user topics

participant_events

Timeline of participant discovered / lost events

topic_metrics

Frequency, sequence-gap and latency schema; data only for builtin discovery topics

peek_bag_samples

Decoded samples from a recorded bag, including ROS 1 .bag (rosbags ships with the package)

list_endpoints

DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer)

The server also offers three MCP prompts, diagnose-dds-bus, inspect-ros2-robot and diagnose-nav2, that give a client the read-only call order, and short server instructions for clients that do not show prompts (see docs/CLIENTS.md).

Walkthroughs against the mock, each with the exact tool calls and payloads, are in examples/. To run the DDS tools against a real bus with several programs and vendors, see examples/dds/README.md (python examples/dds/run_all.py).

Modes

Mode

When to use

Backend

mock

Development, demos, CI, screencasts

Deterministic fixtures

live

ROS2 sourced and on PATH, and/or a DDS backend

ros2 CLI, DDS participant

auto

Detect what is available, else mock (default)

Best available

live and auto degrade instead of failing: if neither ros2 nor a DDS backend comes up, the server serves the mock fixtures and health_check reports mode: "mock" next to requested_mode. Only TOPICFORGE_MODE=mock forces fixtures unconditionally. The live ROS2 adapter shells out to the ros2 CLI, so rclpy does not need to be importable.

DDS backends

pip install topicforge                           # includes Eclipse CycloneDDS where wheels exist (Python 3.10-3.13; Linux x86_64, Windows x64, macOS)
pip install "topicforge[dds]"                    # alias, kept so older commands work; forces a source build elsewhere (needs the Cyclone C library)
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge

TOPICFORGE_DDS_BACKEND accepts mock (default), cyclone, fast and auto (fast, then cyclone, then mock, whichever binding imports). The default mock selects no DDS backend: installing the Cyclone binding is not enough, you must also set TOPICFORGE_DDS_BACKEND=cyclone. With TOPICFORGE_MODE=live and no backend selected, the DDS tools raise DDS module is not active: ... with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and health_check reports dds_backend: "none" plus dds_inactive_note. An explicit value is honoured with or without ros2 on PATH, in any mode except mock. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both ros2 and a DDS backend are up, a composite adapter routes the eight ros2 CLI tools (list_topics, get_topic_info, list_nodes, get_node_info, inspect_tf, graph_diff, sample_messages, analyze_bag) to the CLI and the six DDS tools to the DDS backend; peek_bag_samples reads bags without the CLI.

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. Any other value (rti, opensplice, coredx, intercom, opendds, dust) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in docs/DDS_QUICKSTART.md; error messages are in docs/TROUBLESHOOTING.md.

Configuration reference

Variable

Default

Description

TOPICFORGE_MODE

auto

mock, live or auto

TOPICFORGE_LOG_LEVEL

INFO

DEBUG, INFO, WARNING, ERROR

TOPICFORGE_ROS2_BIN

ros2

Name or path of the ROS2 CLI binary

TOPICFORGE_TELEMETRY

off

Opt-in anonymous telemetry; an unrecognized value aborts startup. See Telemetry

TOPICFORGE_DDS_BACKEND

mock

mock (no DDS backend), cyclone, fast, auto; anything else is rejected

TOPICFORGE_DDS_DOMAIN_ID

0

DDS domain observed (0..232). Joined at startup; changing it needs a restart

TOPICFORGE_MAX_SAMPLE_BYTES

1048576

Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note

Command-line options (not environment variables): --transport stdio|streamable-http (default stdio) and --port N (default 8765, only with streamable-http). The HTTP transport binds 127.0.0.1 only, with DNS-rebinding protection and no authentication; see docs/CLIENTS.md.

Samples with comments are in .env.example. Any invalid value stops the server with topicforge: configuration error: ... and exit code 2, so a typo cannot silently change behaviour.

Limitations

External validation against a simulated robot's ground truth (OmniSim): docs/VALIDATION.md.

  • DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (.github/workflows/demo.yml, .github/workflows/ros2-live.yml). The Fast DDS adapter (TopicForge joining through the fastdds binding) 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.

  • 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.

  • Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and lost_ns is an upper bound of the death.

  • 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.

  • Single domain: the server observes the domain it joined at startup; changing it needs a restart.

  • 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.

  • Fast DDS serves no list_endpoints.

  • sample_messages (live) streams ros2 topic echo until count messages arrive or timeout_s (1..40, default 10, for the whole call) runs out, and returns what arrived with a note (N of M messages within T s, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (payload.header.stamp.sec); timestamp_ns is header.stamp (the publisher's clock, sim time on a simulation), the time in the body of /clock, /tf and /rosout messages (stamp_source payload), or 0 for other headerless types, with stamp_source and received_ns (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; max_array_length (1..65536, or null for no cut) and arrays_summary_only change that, and a cut is listed under _truncated_fields. nan and inf come back as strings. count above 50 is capped with a note.

  • analyze_bag (live) parses ros2 bag info text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates ((n - 1) / span) and latched are added when the bag can be read locally (.db3 with the standard library, .mcap with rosbags), else rates fall back to count / bag duration (frequency_basis). peek_bag_samples reads the file itself, through rosbags, returns the first count messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its timestamp_ns is the message's header.stamp (stamp_source header), the body time of Clock, TFMessage and Log messages (payload) or, without either, the bag record time (stamp_source recorded), and recorded_ns is always the bag record time; bags that embed no message definitions (Humble .db3) are decoded with the Humble definitions, or the distro the bag records, and note says so. Without ros2, bag tools return fixtures: check health_check for mode: "mock" before trusting bag output. health_check also reports sim_clock_published (live only): true when /clock has a publisher, a hint that header stamps may be simulation time.

  • Calls are serialized per backend (one ros2 call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with busy. health_check and peek_bag_samples take no lock.

  • No streaming or push subscriptions: tools are strictly request/response.

Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in issues.

Telemetry

Opt-in, anonymous and off by default. When off, instrumentation returns the handler unchanged: no event is built, no transport is constructed, no network code runs (pinned by tests/test_telemetry.py::test_build_app_off_makes_no_transport_calls).

TOPICFORGE_TELEMETRY=on python -m topicforge

On-values: on, 1, true, yes, enabled. Off-values: unset, off, 0, false, no, disabled. Anything else is a configuration error rather than a silent "off".

When on, each tool call emits one event with exactly six fields:

Field

Example

Notes

tool_name

"list_topics"

One of the sixteen tools, never argument values

latency_ms

12.34

Handler wall-clock duration, 2 decimals

mode

"mock"

Mode of the adapter actually serving: mock or live

version

"0.8.0"

TopicForge server version

session_id

"a1b2c3..."

Random UUID per process, never persisted

success

true

Whether the handler returned or raised

Never sent: topic names, message types or payloads, bag paths or contents, hostnames, usernames, IP addresses, environment variables, error messages. The field set is fenced by tests/test_telemetry.py::test_payload_contains_only_whitelisted_keys; adding a field requires updating this section. The default transport is a structured log line; there is no HTTP endpoint yet. The implementation is in src/topicforge/telemetry/.

Security model

TopicForge is designed for local trust: it runs as a subprocess of your MCP client on a machine you control and inspects your own ROS2 graph, DDS domain and bag files. It is not hardened for adversarial inputs.

  • TOPICFORGE_ROS2_BIN accepts an arbitrary path; treat it the way you treat PATH.

  • analyze_bag and peek_bag_samples open whatever path the client passes (no workspace isolation, no symlink restriction).

  • All ros2 invocations use subprocess.run with an argument list, never shell=True. ROS2 topic names are validated against ^/[A-Za-z0-9_/]+$ first.

  • The server loads no third-party code at startup.

  • No outbound network calls unless telemetry is turned on.

Before exposing TopicForge to untrusted MCP clients (hosted endpoints, shared environments), add path isolation and revisit the TOPICFORGE_ROS2_BIN policy. Vulnerability reports: see SECURITY.md.

Development

git clone https://github.com/yaniswav/TopicForge.git && cd TopicForge
python -m venv .venv && source .venv/bin/activate     # Windows: .venv\Scripts\Activate.ps1
pip install -e ".[dev]"
python -m ruff check src tests
python -m pytest

Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the cyclonedds or fastdds binding skip themselves when it is absent, and the integration marker (real-bus tests) is deselected by default. The Makefile (make check) uses POSIX shell syntax; on plain PowerShell run the commands above. See CONTRIBUTING.md.

Upgrading

TopicForge is pre-1.0; CHANGELOG.md lists every change, including the yanked releases and removed extras. 0.7.0 changed the shape of tool results (contract_version 2): docs/MIGRATION-0.7.md maps every old field to its new one.

Layout

src/topicforge/
  server/      MCP bootstrap, build_app(settings), prompts and instructions
  tools/       thin MCPServer handlers, no backend logic
  services/    input validation, orchestration, adapter factory
  adapters/    ros2_live, ros2_mock, dds_cyclone, dds_fast, common/ (binding-free logic)
  models/      frozen Pydantic schemas, the contract with MCP clients
  config/      settings and mode resolution
  telemetry/   opt-in, off by default
examples/      mock walkthroughs (*.md) and runnable live DDS examples (dds/)
scripts/       real-bus interop checks
docs/          guides, product plan
tests/         pytest suite, mock-only, no ROS2 or DDS required

Layers are strictly separated: handlers never call subprocess, adapters are the only code that talks to a backend, and new backends implement the MiddlewareAdapter protocol in adapters/base.py.

License

MIT, see LICENSE. Integration or support work for a specific ROS 2 / DDS setup: ethvignot.yanis@gmail.com.

Available Tools

16 tools
analyze_bagA
Read-onlyIdempotent

Summarize a ROS 2 bag at path as a BagAnalysis: storage format, duration, message count, per-topic stats and mode_effective. Per topic frequency_hz is (n - 1) / (last - first message time) (frequency_basis topic_span), with first_timestamp_ns, last_timestamp_ns and latched; a latched topic whose messages all fall within 1 s (e.g. /tf_static) has a null frequency_hz. When the bag cannot be read locally or is a .mcap over 200 MiB, the rate falls back to count / bag duration (bag_duration) and note says why. Live mode runs ros2 bag info on .mcap, .db3 or rosbag2_* directories (for ROS 1 .bag use peek_bag_samples); mock mode returns fixture data for any bag-like path, and only mock detects anomalies. Raises an MCP error for a malformed or missing path (live), unparseable output, or without a ros2 CLI. Read-only; no side effects. Duration: ros2 CLI behind the ROS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
pathYesBag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path.

Output Schema

NameRequiredDescription
noteNoWhy the result is less detailed than usual (e.g. rates over the whole bag duration); else `None`.
pathYesBag path as supplied.
topicsYesPer-topic statistics for every topic present in the bag.
start_nsNoRecord time in ns of the bag's first message (rosbag2 `starting_time`): the zero of `peek_bag_samples` `start_s` / `end_s`. `null` when unreadable.
anomaliesNoNotes on gaps or clock jumps; mock mode only.
bag_formatNoDetected container: `mcap`, `db3`, `bag` or `unknown`; `None` for `ros2 bag info` text.
duration_sYesBag duration in seconds.
message_countYesTotal number of messages across all recorded topics.
mode_effectiveYes`live` or `mock` (fixtures).
storage_formatNo`mcap`, `sqlite3`, or other storage identifier when known.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/no-destructive, and the description still adds substantial behavior: the 200 MiB `.mcap` fallback rule, the `bag_duration` basis and `note`, which error conditions raise MCP errors, the 45 s ceiling, and single-call lock serialization. The only small gap is that the `latched`/1 s rule is described operationally without saying how the caller should interpret a null `frequency_hz`.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the return shape, then falls back to computation semantics, modes, error cases and timing — a sensible information hierarchy with no filler sentences. It is dense to the point of being a wall of bolded clauses, which costs a point on scannability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with an output schema, the description still covers modes, format gate, fallback math, error surface, lock contention and timeout — everything needed to call it correctly and to interpret a degraded result. Nothing material is left for the agent to guess.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the parameter is already self-documented, so the baseline is 3; the description nonetheless adds that existence and format are validated live while mock accepts any well-formed path, which is meaning beyond the schema. It largely restates the accepted extension list, which caps it below 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource ('Summarize a ROS 2 bag at `path`') and names the concrete return type (`BagAnalysis`) plus its fields, so the agent knows exactly what it gets back. It also routes ROS 1 `.bag` users to `peek_bag_samples`, distinguishing it from the closest sibling in the same sentence.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly splits live mode (`.mcap`, `.db3`, `rosbag2_*`) from mock mode (any bag-like path, anomaly detection only) and states the alternative (`peek_bag_samples`) for the format this tool rejects. It even gives retry guidance for the `busy` lock error.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

detect_qos_mismatchesA
Read-onlyIdempotent

Explain why DDS readers and writers on a topic do not talk, and who will. Pairs every reader with every writer per topic and returns a MismatchScan: summary (one line, read it first), matched (pairs DDS will connect given the announced QoS; data flow is not observed), reports (incompatible or risky QoS pairs, with participant names, requested vs offered values and the failed rule in details), not_matched (pairs DDS never matches: different partitions or type names; the QoS rules are NOT evaluated for them, and latent_incompatible_policies lists what would also break once fixed), hints (probable typos in orphan topic names, type id notes), plus pairs_checked, topics_scanned, policies_checked and policies_unchecked. Checked: Partition (* and ? wildcards), type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership, DestinationOrder, DataRepresentation, History (risky only, where announced). An empty reports with a non-empty not_matched still means no data flows, and an all-empty result does not prove the bus healthy: discovery shows the QoS DECLARED, not runtime behavior (a 'deadline missed' log with compatible QoS means the writer's real period exceeds the deadline, which TopicForge cannot see). Pass topic to scope the scan. The lists are capped at 200 each (truncated; *_total keep the counts). A late_joiner match is a VOLATILE writer whose reader joined later on the same host: normal. Reports and partition splits carry suggested_fixes: text for a human, never applied by TopicForge; verify_with names the call that confirms the repair. Read-only by architecture. Raises an MCP error when no DDS module is active; mock returns fixtures. Duration: DDS binding behind the DDS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
topicNoOptional DDS topic to scope the scan to: `scan` or `rt/scan`. Omit to scan all topics.

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
hintsYesLeads that are not findings: probable typos, type id notes, incomplete checks.
matchedNoPairs DDS will connect given the announced QoS (data flow is not observed).
reportsYesPairs sharing partition and type with incompatible or risky QoS.
summaryNoOne-line reading of the scan: counts of incompatible, not matched and matched pairs.
truncatedNoTrue when `reports`, `matched` or `not_matched` hit the 200-entry cap (see `*_total`); narrow with `topic`.
not_matchedYesPairs DDS never matches (partition or type name). Non-empty with empty `reports` is NOT all-clear: no data flows between them.
matched_totalNoMatched pairs before the size cap.
pairs_checkedYesSame-topic (reader, writer) pairs examined.
reports_totalNoReports before the size cap.
mode_effectiveYes`live` or `mock` (fixtures).
topics_scannedYesTopics that had at least one endpoint in scope.
policies_checkedYesPolicies compared on every pair.
not_matched_totalNoNot-matched pairs before the size cap.
policies_uncheckedYesWhat the scan does not cover, each with a reason; a clean result says nothing about them.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds plenty beyond annotations: read-only by architecture, raises an MCP error without an active DDS module, runs under a DDS lock with a 45 s bound and `busy` retry, caps lists at 200 with `truncated`/`*_total`, and flags that discovery shows declared QoS rather than runtime behaviour. It also explains that `late_joiner` matches are normal and that suggested fixes are text only.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and the returned scan fields, then the caveats and error/latency notes. It is dense and long, and some content (read-only, return shape) duplicates annotations or the output schema, but nearly every sentence carries operational value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for a scan tool with an output schema: it covers scoping, truncation, lock/duration behaviour, error conditions, and the important false-negative caveat about empty results. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter, and the schema already documents `topic` at 100% coverage, including the omit-to-scan-all behaviour. The description repeats this rather than adding syntax or format details, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource: explains why DDS readers and writers on a topic cannot communicate and reports who can. It clearly differs from siblings like get_topic_info or list_endpoints by describing a pairwise QoS compatibility scan with a named return type.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Says to pass `topic` to scope the scan, and that omitting it scans all topics. It also points at `verify_with` for confirming repairs and warns that an empty `reports` still means no data flows. It does not explicitly state when to prefer a sibling such as get_topic_info, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_node_infoA
Read-onlyIdempotent

ROS 2 graph only; on a DDS-only setup use list_participants. Describe one ROS 2 node as a NodeInfo: the topics it publishes and subscribes to, the services and actions it serves or calls (each {name, type}), its parameters and use_sim_time. Interfaces come from the graph (ros2 node info), which never waits for the node. Parameters come from a read (ros2 param dump) bounded by timeout_s: a node that does not answer in time is a finding, not an error: parameters is null, parameters_note says its executor is probably blocked, and the interfaces are still returned. A value whose name contains password, secret, token, api_key or credential is replaced by <masked> and a long value is cut (truncated, original_size), both said in parameters_note. With params_file, param_diff lists declared values that differ on the node, are declared_only or masked. With several nodes of that name duplicate_count is above 1 and note warns. Raises an MCP error when the node is not on the graph (the message lists close matches), the name is malformed, or without a ros2 CLI. Read-only: the only request TopicForge ever sends is a parameter read (list/get); it sends no other request to a node. Duration: ros2 CLI behind the ROS lock, one call at a time; up to three CLI calls (node list and info, 8 s each at most; the parameter read, at most timeout_s, 20 s); ends within 45 s, less time for the read after a wait; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
nodeYesFully qualified node name as `list_nodes` gives it, e.g. `/robot1/lidar_driver` (a missing leading `/` is added).
timeout_sNoSeconds to wait for the parameter read, 1..20, default 8. A node that does not answer is reported in `parameters_note`, not as an error.
params_fileNoServer path of a ROS 2 parameter `.yaml` (1 MiB max).

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
full_nameYesFully qualified node name, e.g. `/lidar_driver`.
lifecycleNoTrue when `service_servers` has `<full_name>/get_state` (GetState).
param_diffNoFile against node: differences only.
parametersNoParameters sorted by name; `None` when unreadable (see `parameters_note`); an empty list means none.
publishersYesTopics the node publishes.
subscribersYesTopics the node subscribes to.
use_sim_timeNoValue of the node's `use_sim_time` parameter. `None` when unknown: see `use_sim_time_note`.
action_clientsYesActions the node calls.
action_serversYesActions the node serves.
mode_effectiveYes`live` or `mock` (fixtures).
duplicate_countYesNodes using this exact full name; above 1, the data above belongs to one of them.
param_diff_noteNo
parameters_noteNoWhy `parameters` is `None`, or what was masked or cut; `None` when complete.
service_clientsYesServices the node calls.
service_serversYesServices the node offers, its own parameter services included.
use_sim_time_noteNoWhy `use_sim_time` is `None`. `None` when it is set.
param_diff_summaryNoCounts left out: same, live_only.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), yet the description adds substantial non-obvious behavior: a timed-out parameter read is a finding rather than an error (partial result with `parameters_note`), secrets matching password/secret/token/api_key/credential are replaced with `<masked>`, long values are truncated, and duplicate node names raise `duplicate_count`. The duration/lock model (up to three CLI calls, 8 s each, 45 s ceiling) is unusually precise and consistent with the read-only annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and the sibling alternative before any detail, and nearly every sentence carries operational information. It is dense and heavy with bold emphasis, which costs some readability, but there is no filler to cut.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a three-parameter, open-world read tool with an output schema, this covers everything an agent needs: scope, alternative, error conditions, partial-result semantics, masking, time bounds, and concurrency. Nothing required to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real semantic weight beyond the schema: `timeout_s` governs only the parameter read and a miss is reported rather than raised, and `params_file` is explained by its effect (`param_diff` listing differ / declared_only / masked entries). It does not restate the `node` name format, leaving that to the schema, which is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource ("Describe one ROS 2 node as a `NodeInfo`") and immediately enumerates what that record contains: topics, services/actions, parameters, use_sim_time. It is trivially distinguishable from siblings like get_topic_info and list_participants, which the description names as the alternative in the DDS case.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly scopes itself ("ROS 2 graph only") and routes the agent elsewhere when that scope fails ("on a DDS-only setup use `list_participants`"). It also states the failure boundary — an MCP error when the node is absent from the graph, the name is malformed, or no `ros2` CLI exists — and the `busy` retry condition.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_topic_infoA
Read-onlyIdempotent

ROS 2 graph only; on a DDS-only setup use list_endpoints. Describe one ROS 2 topic (fully qualified, e.g. /cmd_vel) as a TopicInfo: counts and, in live mode, publisher_qos / subscription_qos (reliability, durability where transient_local marks a latched topic such as /tf_static, endpoint_count; a null side says why in its _note) and publisher_nodes / subscriber_nodes. Raises an MCP error for a malformed or unknown topic, or without a ros2 CLI. Read-only; no side effects. Duration: ros2 CLI behind the ROS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
topicYesFully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI.

Output Schema

NameRequiredDescription
nameYes
noteNoOne sentence of context, or `None`.
message_typeYes
publisher_qosNoQoS of the publishers. `None` when it was not read: see `publisher_qos_note`.
mode_effectiveYes`live` or `mock` (fixtures).
publisher_countYes
publisher_nodesNoPublisher node names (`ros2 topic info --verbose`); empty when unknown.
subscriber_countYes
subscriber_nodesNoSubscriber node names; empty when unknown.
subscription_qosNoQoS of the subscriptions. `None` when it was not read: see `subscription_qos_note`.
publisher_qos_noteNoWhy `publisher_qos` is `None`. `None` when it is set.
subscription_qos_noteNoWhy `subscription_qos` is `None`. `None` when it is set.

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations by disclosing the ROS lock, the 45 s bound, the single-call-at-a-time serialization, the `busy`/retry path, and the MCP error conditions (malformed/unknown topic, missing CLI). Safety is already covered by readOnly/idempotent hints, so the only minor gap is that it does not say how counts behave when the topic exists but has no endpoints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the routing constraint and then the operation, with a labeled Duration clause at the end. The QoS field enumeration is dense but earns its place; slightly heavy for one sentence's worth of content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers routing, error modes, timing and lock behavior for a single-parameter read tool with an output schema that already explains the return shape. Only minor omission is what happens for a valid topic with zero endpoints (empty lists vs error).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema already constrains the format, so baseline is 3; the description adds value by restating the fully qualified requirement as a precondition for calling and by noting rejection happens before the CLI is reached, which hints at fast-fail behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb + resource: 'Describe one ROS 2 `topic` ... as a `TopicInfo`', with the fully qualified name format given inline. It distinguishes itself from siblings list_topics (list vs describe) and list_endpoints (ROS 2 graph vs DDS-only) explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Opens with the ROS 2 graph constraint and names the exact alternative for a DDS-only setup (`list_endpoints`). It also states the retry condition on `busy`, so the agent knows what to do rather than giving up.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

graph_diffA
Read-onlyIdempotent

ROS 2 graph only. Answer "what changed since it last worked": snapshot the graph (or bag_path) and compare it with baseline, as a GraphDiff. changes are node, topic, type, qos, rate and count differences; added = present now, absent from the baseline. not_compared names what a pair cannot support: nodes and counts need live against live, rates bag against bag, writer QoS a DDS backend. /rosout and /parameter_events count noise is only tallied in builtin_changes_count. The first call stores a snapshot and says so in note. Snapshots stay in memory (16, lost on restart). Raises an MCP error for an unknown id, a bad bag path or without a ros2 CLI. Mock mode: the first call reports 3 changes, the next 0. Read-only. Duration: ros2 CLI behind the ROS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
bag_pathNoSnapshot this bag instead of the live graph.
baselineNo`previous` (last snapshot), a `snap-N` id from `snapshot_ids`, or a bag path.previous

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
changesYes
currentYes
baselineNo`null` without baseline.
added_countYes
baseline_idNo`null` without baseline (`note`).
snapshot_idYesStored snapshot id.
not_comparedYesSkipped comparison and why.
snapshot_idsYesHeld ids, oldest first.
changed_countYes
removed_countYes
mode_effectiveYes`live` or `mock` (fixtures).
builtin_changes_countYesCount changes on `/rosout`, `/parameter_events`: not in `changes`.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only/no-destructive annotations, it discloses stateful snapshot storage and its 16-entry in-memory cap, first-call note behavior, MCP errors, mock mode behavior, and lock/duration semantics. These are concrete behavioral traits that materially help an agent invoke it correctly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded and most detail is useful, but the description is long and explains return fields like changes, added, and not_compared despite an output schema existing. 'Read-only' also largely repeats the annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a stateful diff tool with many edge cases, the description covers live vs bag modes, baseline selection, error conditions, snapshot retention, mock behavior, and timing constraints. With an output schema present, it does not need to explain every return value in depth.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already carries the parameter semantics. The description mirrors bag_path and baseline meanings and adds first-call context, but it does not add much syntax or format detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific operation (snapshot and compare), resource (ROS 2 graph), and output shape (GraphDiff). It distinguishes the tool from single-resource siblings by focusing on what changed between a current snapshot and a baseline.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly frames the use case as 'what changed since it last worked' and explains live vs bag_path plus baseline options. It also lists pair-wise limitations in not_compared, but it does not explicitly name sibling alternatives for non-diff tasks.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

health_checkA
Read-onlyIdempotent

Report TopicForge environment state as a HealthReport: mode, ros_backend, dds_backend, ros_tools_available, ros2_distro, rmw_implementation, dds_domain_id and observed_domain_note (only the domain joined at startup is observed), server version and sample cap; for a live DDS backend also observer_started_ns / now_ns (nothing earlier was watched) and the discovery tracker status tracker_* (errors, evictions or a stale pass mean gaps). live with ros_backend none is a DDS-only setup: use list_endpoints. With dds_backend none, dds_inactive_note says why. payload_decoding is disabled (DDS user-topic payloads are not decoded); dds_security is not_supported. Always succeeds: call it first when something looks wrong. Read-only; no side effects. Duration: never waits for a lock and never runs the ros2 CLI; it answers at once.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
modeYesMode serving: `mock` or `live` (`live` with `ros_backend` `none`: DDS tools only); may differ from `requested_mode` after a fallback.
noteNoOne sentence of context, or `None`.
now_nsNoServer wall-clock time (ns since epoch) when this report was built.
rmw_sourceNo`env`, `ros2_cli`, `distro_default` (not verified on the graph) or `none`.
dds_backendNoDDS backend serving: `mock`, `cyclone`, `fast`, or `none` (DDS module inactive).
ros2_distroNo`ROS_DISTRO` if set (disclosed by design under the local-trust model).
ros_backendNoROS 2 backend: `ros2_cli`, `mock` or `none`.
dds_securityNoDDS Security is not handled: protected endpoints and data are invisible.
dds_domain_idNoDDS domain id observed when the DDS module is active.
requested_modeYesMode requested by configuration (may be `auto`).
ros2_availableYesWhether a `ros2` CLI is on PATH.
server_versionYesTopicForge version.
tracker_errorsNoTracker passes that raised; non-zero means gaps.
tracker_passesNoCompleted discovery tracker passes since start.
tracker_runningNoWhether the discovery tracker thread is alive (Cyclone); `None` without a tracker.
contract_versionNoOutput contract version; changes when a field is renamed, removed or retyped.
max_sample_countYesServer cap on samples per call; larger requests are clamped.
payload_decodingNo`disabled`: DDS user-topic payloads are not decoded.
dds_inactive_noteNoWhy `dds_backend` is `none` while ROS 2 serves; else `null`.
ros_security_noteNoSet when `ros_security_enabled`: what stays invisible.
rmw_implementationNoROS 2 middleware, e.g. `rmw_fastrtps_cpp`; `None` when `rmw_source` is `none`.
observer_started_nsNons when the DDS observer joined; nothing earlier was watched. `None` without one.
ros_tools_availableNoTrue when the ROS 2 tools can run (`ros_backend` is not `none`); on a DDS-only setup use `list_endpoints`.
sim_clock_publishedNoWhether `/clock` had a publisher at the last graph read (stamps may be sim time); `None` in mock or without a recent read.
middleware_availableNoTrue when a DDS backend serves; else whether the configured binding is importable.
observed_domain_noteNoWhich DDS domain is observed: only the one joined at startup; others are invisible.
ros_security_enabledNo`ROS_SECURITY_ENABLE` is `true`; `None` unless the ros2 CLI serves.
tracker_last_pass_nsNons of the last tracker pass; far older than `now_ns` means stale lifecycle.
payload_decoding_noteNoReason for `payload_decoding`.
ros_security_strategyNo`ROS_SECURITY_STRATEGY` when set to a valid value.
tracker_cache_evictionsNoDiscovery entries dropped from a full tracker cache; non-zero means the bus is bigger than listed.
ros_security_keystore_setNoWhether a keystore variable is set (the path is never shown).

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only, idempotent, non-destructive behavior, and the description adds substantial context beyond that: it always succeeds, never waits for a lock, never runs the ros2 CLI, and answers at once. It also discloses field-level semantics such as observed_domain_note only reflecting the startup domain and tracker_* errors indicating gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is a single front-loaded paragraph with no filler, and the dense field list earns its space by attaching operational meaning to fields like observed_domain_note and tracker_*. The critical advice to call it first is stated clearly at the end.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description does not need to explain return values, but it still adds the failure-mode and field-semantics context an agent needs to interpret the report. It covers the always-succeeds guarantee, DDS-only and no-DDS cases, and the limited payload decoding and security scope.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the baseline is 4 and there is no input schema detail the description needs to compensate for. The description appropriately focuses on the report and its semantics rather than inventing parameter guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: report TopicForge environment state as a HealthReport. It distinguishes the tool from siblings by naming list_endpoints for DDS-only setups and by explaining how health_check differs from a live data-listing/query tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear usage guidance: call it first when something looks wrong. It also names the relevant alternative, list_endpoints, for the specific case where ros_backend is none, and explains what dds_inactive_note means when dds_backend is none.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_tfA
Read-onlyIdempotent

ROS 2 graph only. Rebuild the TF tree from the transforms on /tf_static (latched) and /tf (listened to for window_s) and flag what breaks lookups. Returns a TfReport: frames (parent, kind static/dynamic/both, age_s, stale, observed_frequency_hz on wall time, sim_frequency_hz on stamps), roots, counts and findings {kind, frames, text}: stale (a frame trails the newest /tf stamp by more than stale_after_s), two_parents, disconnected (several roots), static_and_dynamic, cycle, no_path. Ages and rates use header stamps, so they hold on sim time (real_time_factor far from 1 means sim). Give source_frame and target_frame for the path between them. It also reads /robot_description and flags URDF links with no frame (declared_not_published). Empty frames when nothing is published (read note). The listening is capped (3000 messages): echo can drop messages at high rates, so the tree is exact but frequencies are approximate (truncated). Raises an MCP error without a ros2 CLI. Mock mode: a fixed 5 s capture of the demo robot with one stale frame. Duration: ros2 CLI behind the ROS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
window_sNoSeconds to listen to `/tf`, 1..20, default 5.
source_frameNoFrame name, e.g. `base_link`. Give `source_frame` and `target_frame` together to get the chain between them in `path`.
target_frameNoSee `source_frame`.
stale_after_sNoA frame is stale past this many seconds of stamp time, default 2.

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
pathNoFrames from `source_frame` to `target_frame`.
hintsYes
rootsYesFrames with no parent; one in a sound tree.
framesYesSorted by name.
findingsYesEmpty when the tree is sound.
window_sYes
path_noteNoWhy `path` is `None`, or context.
truncatedYesMessages were dropped or the cap was hit.
urdf_noteNo
frame_countYes
stale_countYes
urdf_sourceNo
reference_nsYesNewest `/tf` stamp: the clock for `age_s`.
stamp_span_sYes
static_countYesFrames with a static transform.
dynamic_countYesFrames with a dynamic transform.
message_countYesTransforms captured, repeats included.
mode_effectiveYes`live` or `mock` (fixtures).
transform_countYesDistinct parent-child pairs.
urdf_link_countNo
window_actual_sYes
real_time_factorYesStamp time over wall time: about 1 on a real robot, other on sim.
frames_not_in_urdfNoTF frames not in the URDF (`map`, `odom` are normal).
declared_frames_missingNoURDF links with no TF frame.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover the read-only/idempotent profile, and the description adds substantial operational context beyond that: MCP error without a `ros2` CLI, mock-mode behavior, 45 s duration bound, ROS-lock serialization with a `busy` retry, 3000-message listen cap with `truncated` flag, and wall-time vs stamp-time frequency caveats. It also discloses what empty results mean.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose before the report schema and operational caveats, and nearly every clause carries information. It is a dense wall of text, however, and the TfReport field enumeration partly duplicates the output schema, making it heavier than needed.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still names the finding kinds an agent must reason over (stale, two_parents, disconnected, cycle, no_path) and covers failure modes, time-base semantics, and mock behavior. Nothing needed for correct invocation or interpretation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description reinforces that `window_s` applies to `/tf` only (not latched `/tf_static`) and restates `stale_after_s` in stamp-time terms, but adds little that the schema does not already spell out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Rebuild the TF tree from the transforms on /tf_static and /tf') plus its scope ('ROS 2 graph only'), which separates it from the topic/node/DDS inspection siblings. An agent immediately knows this is the transform-tree analysis tool rather than a generic graph lister.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clear context is given: use source_frame+target_frame to get the chain between them, read `note` when `frames` is empty, retry on `busy`. It lacks explicit when-not-to-use guidance or a named alternative, but no sibling overlaps with TF analysis, so the routing risk is low.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_endpointsA
Read-onlyIdempotent

List every DDS endpoint (writer and reader) announced on the bus as an EndpointListing; one EndpointInfo each with role, dds_topic (raw, e.g. rt/scan), ros_topic (/scan; null with ros_topic_note for a non-ROS topic), type_name, type_id, the owning participant_guid with participant_name, and a structured qos. Use it instead of parsing peek_dds_samples and joining GUID prefixes. Large buses: pass fields compact to replace each qos by a one-line qos_summary and drop type_id, announced_ns and ros_topic_note; the default full is verbose, so scope with topic or participant_guid to read one topic's full QoS. Orphans: by_topic rolls endpoints up per topic with writer_count, reader_count, orphan (no_reader = a writer nobody subscribes to, no_writer = a reader nobody publishes to) and the union of partitions. In qos, a duration or policy of None is infinite/unset or not announced. Among EXCLUSIVE writers the live one with the highest ownership_strength delivers; which one owns an instance is runtime state TopicForge cannot observe. Departed endpoints: a leaving participant's endpoints are remembered (last 200, 1 h) and shown in by_topic as departed_writers / departed_readers (listed in endpoints only with include_departed), so a topic that lost its only writer is explained in one call. Topic filter: rt/scan and scan match each other; a filter matching nothing returns a note with the closest topics. This lists discovery facts, not data flow: it cannot tell a silent or hung writer from a healthy one; pair it with detect_qos_mismatches. The observer's own endpoints are excluded unless include_observer; ROS 2 service and action endpoints (rq/, rr/, rs/, rp/, ra/, ros_discovery_info) unless include_internal. Capped at 500 endpoints (truncated, total); by_topic covers all matches. Read-only. Raises an MCP error when no DDS module is active; mock returns a fixture. Duration: DDS binding behind the DDS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
topicNoOnly endpoints on this DDS topic: `scan` or `rt/scan` (exact name first, then the alternate form). Omit for every topic.
fieldsNo`full` (default) or `compact`: one-line `qos_summary` instead of the full `qos`, no `type_id`, `announced_ns` or `ros_topic_note`. Use `compact` on large buses.full
domain_idNoIgnored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value.
include_departedNoAlso list endpoints whose participant left (flagged with `gone_ns`); `by_topic` reports them either way. Default false.
include_internalNoAlso list ROS 2 service and action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`), most of a small robot's endpoints. Default false.
include_observerNoInclude TopicForge's own observer endpoints. Default false.
participant_guidNoOnly endpoints of this participant (a guid from `list_participants`). Omit for all.

Output Schema

NameRequiredDescription
noteNoSet when a `topic` filter matched nothing: the closest known topics.
hintsNoFacts true of every endpoint, stated once (e.g. liveness is not observed).
totalYesEndpoints in the discovery cache before any filter.
fieldsNoDetail level served: `compact` drops per-endpoint QoS detail, type id and notes.
by_topicYesRoll-up over every matching endpoint.
returnedYes
domain_idYes
endpointsYesMatching endpoints, capped (see `truncated`).
truncatedYesTrue when matching endpoints exceeded the cap.
snapshot_nsYesWall-clock time of the snapshot, ns since epoch.
observer_guidNoGUID of TopicForge's own participant, `None` in mock.
mode_effectiveYes`live` or `mock` (fixtures).
departed_endpoint_countNoDeparted endpoints matching the filters; listed in `endpoints` only with `include_departed`.
hidden_internal_endpoint_countNoService/action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`) left out while `include_internal` is false.
excluded_observer_endpoint_countNoTopicForge's own endpoints left out of `endpoints` (counted in `total`).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (read-only, idempotent, non-destructive), yet the description adds substantial behavior beyond that: a 500-endpoint cap with `truncated`/`total`, a 200-entry/1-hour departed-endpoint cache, `busy` lock errors within a 45s bound, an MCP error with no active DDS module, mock-fixture behavior, and that `domain_id` is ignored and only range-checked. It even discloses semantic limits (cannot distinguish silent/hung writers, cannot observe instance ownership at runtime).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core purpose and avoids fluff, but it is a long, bold-header-laden block; a few clauses (mock fixture, duration lock detail) could be tightened without loss. Every sentence remains informational rather than repetitive, so it stays readable for a discovery-heavy tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return shape need not be restated, and the description fills the remaining gaps: error modes, truncation, filtering interplay, departed-endpoint semantics, QoS `None` meaning, and orphan definitions in `by_topic`. Nothing an agent needs to call it correctly appears missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline would be 3; the description still adds value by explaining cross-parameter intent, e.g. that `compact` exists specifically for large buses and drops specific fields, that `topic` accepts either `rt/scan` or `scan`, and that `domain_id` does not switch domains. Most literal syntax is already in the schema, so it stops just short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource ('List every DDS endpoint... announced on the bus') and enumerates the returned fields, so an agent knows exactly what it gets. It explicitly differentiates from sibling peek_dds_samples ('instead of parsing peek_dds_samples and joining GUID prefixes') and names detect_qos_mismatches as a complement rather than a substitute.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit routing: use this instead of parsing peek_dds_samples, use `fields: compact` on large buses, scope with `topic`/`participant_guid` to read full QoS on one topic, and pair with detect_qos_mismatches because this tool cannot judge writer health. It also states what is excluded (observer, ROS 2 internal endpoints) and how to include them, leaving little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_nodesA
Read-onlyIdempotent

ROS 2 graph only; on a DDS-only setup use list_participants. List the nodes on the ROS 2 graph (or the mock graph) from one ros2 node list call as a NodeListing: name, namespace, full_name, duplicate_count, lifecycle (managed node; state not read, lifecycle_note if unknown), by_namespace counts. duplicates lists full names used by several nodes (a per-node tool then answers for one of them, unidentified). Nodes starting with an underscore are hidden and a node on another ROS_DOMAIN_ID is invisible. Empty nodes when the graph has none. Use get_node_info for one node's interfaces. Raises an MCP error without a ros2 CLI. Read-only; cheap (two CLI calls). Duration: ros2 CLI behind the ROS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
nodesYesNodes on the graph, one entry per distinct full name, sorted by name.
totalYesDistinct node names on the graph (no cap applies).
returnedYesLength of `nodes`.
truncatedYesTrue when `returned` is less than `total` because of a cap.
duplicatesYesFull names that appear more than once on the graph. Empty when every name is unique.
by_namespaceNoDistinct full names per namespace (`/` is the root), sorted by namespace.
lifecycle_noteNoWhy `lifecycle` could not be determined (service list failed), else `None`.
mode_effectiveYes`live` or `mock` (fixtures).
lifecycle_countNoNodes with `lifecycle` true.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent annotations it discloses concrete operational traits: raises an MCP error without the `ros2` CLI, completes within 45 s, serializes behind the ROS lock and can return a `busy` error requiring retry, and returns an empty `nodes` list rather than failing on an empty graph. It also notes hidden underscore-prefixed nodes and nodes on other `ROS_DOMAIN_ID`s are invisible — a genuine discovery caveat.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the ROS 2 vs DDS scoping rule and error/duration notes bolded for scanning. The middle sentence enumerating every `NodeListing` field is dense and slightly over-stuffed given an output schema exists, but nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers call path, failure modes, timing, concurrency, visibility exclusions and empty-result semantics, and the return structure is backed by an output schema. An agent has everything needed to invoke and interpret this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, which is the baseline-4 case; there are no argument semantics to explain. The description instead spends its words on return shape and error behavior, which is the right allocation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the nodes on the ROS 2 graph') and immediately scopes it against siblings: `list_participants` for DDS-only setups and `get_node_info` for a single node's interfaces. The agent can pick the right tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit routing rules are given in both directions: use this tool on the ROS 2 graph, use `list_participants` on a DDS-only setup, and use `get_node_info` to get one node's interfaces. Preconditions (ros2 CLI required) are also stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_participantsA
Read-onlyIdempotent

List DDS participants observed on the bus as a ParticipantListing: guid, vendor (read from the GUID prefix, unknown for vendors that do not put theirs there, e.g. Dust DDS and RTI Connext; see vendor_source), optional name (the announced EntityName, e.g. lidar_driver; a participant with none is normal, not a fault: identify it by its topics), hostname, domain_id and is_observer (TopicForge's own read-only participant, listed like any other). Lifecycle: status (active / left), first_seen_ns / last_seen_ns (TopicForge's clock), announced_ns (DDS source timestamp) and, once left, lost_ns + lost_time_source: an upper bound of the death (exact after a clean shutdown; after a crash the lease expiry, so up to one lease earlier: 10 s Cyclone, 20 s Fast DDS, 100 s RTI). Restarts: a restarted node is a new GUID with the same name; restarts_by_name gives lives (GUIDs) and departures per name, so do not count GUIDs by hand (seen_count is not a restart count). Cyclone tracks discovery in the background; right after start the call waits up to 3 s for it. Only the domain joined at startup is observed (health_check dds_domain_id): a participant on another domain is INVISIBLE here, and domain_id does not switch domains (restart with TOPICFORGE_DDS_DOMAIN_ID). Works at the raw DDS layer, so non-ROS participants appear too. Read-only by architecture: it cannot publish, modify QoS or alter the bus. Raises an MCP error when no DDS module is active (install topicforge[dds], set TOPICFORGE_DDS_BACKEND=cyclone); the mock backend returns fixtures. Duration: DDS binding behind the DDS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
domain_idNoIgnored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value.

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
totalYes
returnedYes
domain_idYesDDS domain observed (joined at startup).
truncatedYes
participantsYes
restarts_noteNoSet when unnamed participants were left out of `restarts_by_name`.
mode_effectiveYes`live` or `mock` (fixtures).
restarts_by_nameNoNames seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name.
restarts_by_nodeNo`restarts_by_name` keyed by ROS 2 node full name.

TDQS

A4.2/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite annotations already declaring read-only and idempotent behavior, the description adds substantial behavioral detail: startup discovery wait up to 3 s, visibility limited to the startup domain, mock backend behavior, MCP errors when no DDS module is active, lock contention with a busy error, and a 45 s duration bound. This is unusually complete transparency beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but front-loaded and structured with bold sections, and almost every sentence conveys operational meaning. Some return-field detail may duplicate the output schema, but the field semantics it adds, such as unknown vendors and optional names, are useful rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex DDS discovery tool with an output schema and safety annotations, the description covers the important operational gaps: domain visibility, restart counting, lifecycle timestamps, error conditions, backend requirements, and duration limits. An agent has enough context to invoke and interpret the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is one parameter and schema description coverage is 100%, so the schema already explains that domain_id is only range-checked and does not switch domains. The description reinforces that constraint but does not add syntax or format details beyond what the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: list DDS participants observed on the bus, returned as a ParticipantListing. It also distinguishes the tool from ROS-graph tools by noting it works at the raw DDS layer, so non-ROS participants appear too.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is implied through domain restrictions and restart semantics, but the description never explicitly names alternatives such as participant_events or list_nodes and never says when to choose this tool over them. An agent can infer the use case, but the guidance is not explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_topicsA
Read-onlyIdempotent

ROS 2 graph only; on a DDS-only setup use list_endpoints. List the topics on the ROS 2 graph (or the mock graph) as a TopicListing: each has name, message_type, publisher_count and subscriber_count; mode_effective tells a real graph from fixtures. QoS is not listed: use get_topic_info. Empty topics when the graph has none or live discovery times out. Raises an MCP error without a ros2 CLI. Read-only; no side effects. Duration: ros2 CLI behind the ROS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
totalYes
topicsYes
returnedYes
truncatedYes
mode_effectiveYes`live` or `mock` (fixtures).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnly/idempotent annotations: discloses empty-result semantics, MCP error behavior without the `ros2` CLI, a 45 s duration bound, and lock contention with a `busy` error requiring retry. That is exactly the operational context an agent needs before calling a discovery tool that can block on a lock.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the scope constraint and then uses bolded fragments for empty results, errors, and duration, so it scans well. Slightly dense and includes one redundant phrase ('Read-only; no side effects') already covered by annotations, but nearly every sentence carries distinct information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even though an output schema exists, the description adds result-field meaning (`mode_effective`, publisher/subscriber counts) and the critical edge cases: empty topics, discovery timeout, missing CLI, and lock contention. Nothing an agent needs to decide whether to call it or interpret its failure modes is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero parameters the baseline is 4, and the description correctly avoids inventing parameter details. It instead explains the shape of what comes back, which is useful but belongs more to the output schema than to parameter semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the topics on the ROS 2 graph') and immediately scopes it against the DDS-only alternative by naming `list_endpoints`. An agent can distinguish it from siblings like `get_topic_info` and `list_endpoints` without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes usage: ROS 2 graph only, use `list_endpoints` for DDS-only setups, and use `get_topic_info` because QoS is not included here. It also names the error condition (missing `ros2` CLI) and the retry case (busy lock), which is when-to-use guidance no sibling name alone would provide.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

participant_eventsA
Read-onlyIdempotent

Return DDS participant lifecycle events (discovered / lost) from a recent window, e.g. 'who left in the last 5 minutes?', as a ParticipantEventListing, newest first: guid, event_type, vendor, timestamp_ns, time_source, observed_ns, age_s (seconds before the response; live only), optional name (announced name), hostname and domain_id. time_source is dds_source_timestamp (timestamp of the announcement or dispose) or observed_local (when TopicForge noticed, weakest). Crash caveat: a lost timestamp is an upper bound of the death: exact after a clean shutdown, the lease expiry after a crash, so the process died up to one lease earlier (10 s Cyclone, 20 s Fast DDS, 100 s RTI); the two cases cannot be told apart. Restarts: a restarted node is a new participant (one lost and one discovered, different guid, same name); restarts_by_name gives lives and departures per name over the window, so do not count GUIDs by hand. Capped at 200 events (truncated, note; reduce lookback_s). TopicForge only knows what happened since it started watching (health_check.observer_started_ns). Backends: Fast DDS uses listener callbacks; Cyclone polls discovery in the background every 0.5 s, so a participant cycle faster than the reader's history depth between two passes can be missed; mock returns a fixture timeline. Right after start the call waits up to 3 s for discovery. Read-only by architecture. Raises an MCP error when no DDS module is active. Duration: DDS binding behind the DDS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
domain_idNoIgnored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value.
lookback_sNoWindow in seconds, 1..86400, default 300. A larger window may hit the 200-event cap.

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
totalYes
eventsYes
returnedYes
domain_idYesDDS domain observed (joined at startup).
truncatedYesTrue when cut to the 200-event cap.
restarts_noteNoSet when unnamed participants were left out of `restarts_by_name`.
mode_effectiveYes`live` or `mock` (fixtures).
restarts_by_nameNoNames seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name. Window only.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only/idempotent/non-destructive, yet the description adds far more: the crash caveat (lost timestamp is an upper bound, lease expiry of 10s/20s/100s), restart semantics, the 200-event cap with truncated/note, the observer-start limitation, backend-specific behavior (Cyclone's 0.5s poll can miss fast cycles), an MCP error when no DDS module, and lock/busy/duration behavior. This is behavior the agent cannot get from annotations or schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and output shape are front-loaded, and bold section labels (crash caveat, restarts, backends) make the dense block scannable. It is long, but nearly every clause carries distinct behavioral information for a genuinely complex tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still clarifies the non-obvious return semantics (time_source strengths, age_s being live-only, restarts_by_name), and covers the operational constraints an agent needs. Nothing required to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both parameters are already documented, including the 'domain_id is ignored' caveat and lookback_s range. The description only restates the window-to-cap relationship ('reduce `lookback_s`'), which adds little beyond the schema's own note that a larger window may hit the cap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return DDS participant lifecycle events (`discovered` / `lost`)') with a concrete example query ('who left in the last 5 minutes?'). The event/lifecycle focus clearly distinguishes it from sibling list_participants, which reports current membership rather than a history of joins and leaves.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a clear usage scenario via the example query and actionable recovery steps ('reduce `lookback_s`' when truncated, 'retry' on `busy`). It never explicitly names a sibling (e.g. list_participants) as the alternative for the current-state case, so the when-not condition is left implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

peek_bag_samplesA
Read-onlyIdempotent

Peek up to count samples from a recorded bag file as a SampleResult: offline, for post-mortem (peek_dds_samples and sample_messages are live). Formats, by extension: MCAP (.mcap), rosbag2 SQLite (.db3), ROS 1 (.bag). Returns the first count messages of the topic in recording order, not the last (sample_messages keeps the newest log matches). start_s / end_s limit it to a bag-relative time range, empty with a note past the end; on /rosout, min_log_level and node keep the first count matching logs in range. Clocks: timestamp_ns is header.stamp when the message has a top-level header (stamp_source header), else the time in the body of Clock, TFMessage or Log (payload), else the bag record time (recorded); recorded_ns is always the record time. Each payload carries _decode_status (full / partial / raw). count defaults to 5, clamped to 50. Uses the rosbags library and needs the ROS 2 side of the runtime (an error on a DDS-only setup). Mock returns fixtures on canned paths. A bag without embedded definitions (Humble .db3) is decoded with those of its recorded distro, or Humble; note says which. Arrays over 4096 elements are cut and note lists them. Read-only by architecture. Raises an MCP error when the path does not exist, the topic is not in the bag, or rosbags is missing. Summary and rate: LaserScan, Odometry, Imu, Image and PointCloud2 samples carry a summary of the whole message (other types: null); rate gives frequency, interval spread and a verdict measured on recorded_ns (the bag record time of the returned messages). Use count >= 10 (50 above 20 Hz); a multi-publisher topic such as /tf reads erratic without being broken. Duration: takes no lock (in-process file read, so it runs while a ROS 2 or DDS call is busy); the time follows the bag's size and how deep the first samples are.

NameRequiredDescriptionDefault
nodeNo`/rosout` only: keep logs of this node and its child loggers, e.g. `/robot1/driver` (also `driver`, `robot1.driver`). Exact name, never a prefix. Other topics: error.
pathYesBag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path.
countNoMessages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower.
end_sNoOnly messages recorded before this bag-relative second (exclusive), default to the end.
topicYesFully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI.
start_sNoOnly messages recorded at or after this many seconds from the bag's first message (`analyze_bag.start_ns`), default from the start.
min_log_levelNo`/rosout` (`rcl_interfaces/msg/Log`) only: keep messages at or above this level (debug 10, info 20, warn 30, error 40, fatal 50). Other topics: error.

Output Schema

NameRequiredDescription
noteNoWhy `samples` is empty or limited, when not obvious; else `None`.
rateNoHow the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10.
countYesNumber of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50).
topicYes
samplesYesThe samples, in backend order.
mode_effectiveYes`live` or `mock` (fixtures).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only/idempotent/closed-world, and the description adds substantial context beyond them: which error conditions raise an MCP error, that it runs without a lock while other calls are busy, that it needs the ROS 2 side of the runtime, decode-status semantics, clamping behavior, and rate/verdict caveats for multi-publisher topics. Nothing contradicts the annotations — 'read-only by architecture' and offline/mock behavior match readOnlyHint and openWorldHint=false.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded and well-organized: purpose and null-hypothesis sibling contrast come first, then time range, clocks, errors, rate, and duration in ordered blocks. It is dense and slightly over-stuffed — the clock-resolution and duration paragraphs border on reference documentation — but almost every sentence carries invocation-relevant information, and repeated facts (count default/clamp) are a minor redundancy with the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 7-parameter tool with an output schema, the description is more than complete: it explains result fields (`_decode_status`, `timestamp_ns` sources, `summary`, `rate`), failure modes, and environment prerequisites, so an agent can predict both success and error behavior. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns extra credit by explaining cross-parameter semantics the schema states only piecewise — that `min_log_level`/`node` apply only on `/rosout` and jointly keep the first `count` matching logs within the `start_s`/`end_s` window, and that `start_s` is measured from the bag's first message. Remaining parameters (path, topic) get no added meaning beyond schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence names a specific verb and resource (peek samples from a recorded bag file) and immediately scopes it as offline/post-mortem, explicitly contrasting with `peek_dds_samples` and `sample_messages` which are live. An agent can distinguish it from every sibling without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states when to use this tool (offline post-mortem on recorded bags) versus the live alternatives by name, and notes the ordering contract (first `count` messages, not newest like `sample_messages`). It also flags the ROS 1 `.bag` support that `analyze_bag` lacks, giving an explicit selection criterion between the two bag readers.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

peek_dds_samplesA
Read-onlyIdempotent

Peek recent samples on a raw DDS topic as a SampleResult, like sample_messages but read from the DDS layer (works without ROS 2). count defaults to 5, clamped to 50. (a) The builtin discovery topics (DCPSParticipant, DCPSSubscription, DCPSPublication) return structured discovery records: on Cyclone the CURRENT state (one per live participant or endpoint), not recent events (use participant_events); DCPSPublication / DCPSSubscription are the raw data behind list_endpoints. The topic may be /scan, scan or rt/scan. (b) User topics: payload decoding is DISABLED on every backend, so the call returns count 0, empty samples and a note; that does NOT mean the topic is silent. Use list_endpoints for its presence, writers, readers and QoS. A user topic not announced on the bus raises an error. Right after start the call waits up to 3 s for discovery. Read-only by architecture: it cannot publish. Raises an MCP error when no DDS module is active or the topic is not announced. Duration: DDS binding behind the DDS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
countNoMessages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower.
topicYesDDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only.

Output Schema

NameRequiredDescription
noteNoWhy `samples` is empty or limited, when not obvious; else `None`.
rateNoHow the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10.
countYesNumber of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50).
topicYes
samplesYesThe samples, in backend order.
mode_effectiveYes`live` or `mock` (fixtures).

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only and open-world annotations, the description discloses important behavior: payload decoding is disabled for user topics, such calls return a note and empty samples rather than proving silence, discovery can wait up to 3 seconds, and lock contention produces a retryable busy error. It also explains that the tool cannot publish and raises MCP errors when no DDS module is active or the topic is not announced.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and sibling differentiation, then organized into builtin-topic and user-topic cases. It is dense and long, with some repetition of schema details, but most clauses carry operationally important edge cases.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, error modes, discovery timing, and lock behavior, the description covers what an agent needs to call it correctly. An output schema exists, so return-value structure does not need to be spelled out further.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both parameters, including the default, cap, and accepted topic forms. The description reinforces this with examples like `/scan`, `scan`, and `rt/scan`, but adds little meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: peeking recent samples on a raw DDS topic, returning a SampleResult. It explicitly distinguishes itself from `sample_messages` by reading from the DDS layer and working without ROS 2.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit branching guidance: builtin discovery topics return current-state records and should use `participant_events` for recent events, while `list_endpoints` should be used for user-topic presence, writers, readers, and QoS. It also explains error conditions and retry behavior when the DDS lock is busy.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sample_messagesA
Read-onlyIdempotent

ROS 2 graph only; on a DDS-only setup use list_endpoints or peek_dds_samples on DCPSPublication / DCPSSubscription. Collect up to count live messages from a ROS 2 topic, waiting at most timeout_s seconds, as a SampleResult. Live mode streams ros2 topic echo with the publishers' QoS (latched topics work) and returns what arrived by the deadline: count is the actual number and note says N of M messages and why it is short (no publisher, silent, slow or too large to print in time; a latched topic usually holds one message, so use count 1). Each sample has payload (nested named values), timestamp_ns (the publisher's clock, sim time on a simulation, 0 for a headerless message such as std_msgs/String; see stamp_source) and received_ns (wall clock when the CLI printed it). Arrays: arrays, strings and bytes are cut at 128 elements by default (a 541-beam LaserScan loses beams 128 and up); cut fields are listed in payload._truncated_fields and note. Raise max_array_length (up to 65536, or null) or set arrays_summary_only. A message over the 1 MiB cap (TOPICFORGE_MAX_SAMPLE_BYTES) is dropped with a note. Mock mode returns demo-robot samples instantly. Raises an MCP error without a ros2 CLI, for an unknown topic, or when the CLI fails. Read-only; never publishes. Logs: min_log_level / node (/rosout only) stream until timeout_s (at most 2000 messages) and return the NEWEST count matches in stamp order; peek_bag_samples returns the FIRST matches. Summary and rate: LaserScan, Odometry, Imu, Image and PointCloud2 samples carry a summary of the whole message (other types: null); rate gives frequency, interval spread and a verdict measured on received_ns (when the CLI printed each message). Use count >= 10 (50 above 20 Hz); a multi-publisher topic such as /tf reads erratic without being broken. Duration: ros2 CLI behind the ROS lock, one call at a time; returns within timeout_s (at most 40) plus about 3 s to stop the CLI and decode; ends within 45 s, less time to collect after a wait; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
nodeNo`/rosout` only: keep logs of this node and its child loggers, e.g. `/robot1/driver` (also `driver`, `robot1.driver`). Exact name, never a prefix. Other topics: error.
countNoMessages to wait for, default 5, at most 50 (more is capped and `note` says so); 0 returns nothing. Fewer arrive when the topic is slow or `timeout_s` runs out.
topicYesFully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI.
timeout_sNoSeconds to wait, 1..40, default 10, covering topic lookup and CLI start-up. The call returns within `timeout_s` plus a few seconds, with what arrived and a `note`. Raise it for a topic slower than 1 Hz.
min_log_levelNo`/rosout` (`rcl_interfaces/msg/Log`) only: keep messages at or above this level (debug 10, info 20, warn 30, error 40, fatal 50). Other topics: error.
max_array_lengthNoLongest array, string or bytes value returned in full, 1..65536, default 128 (a 541-beam `LaserScan` is cut to 128 ranges; its `summary` still covers all). Longer values are cut and listed in `_truncated_fields`. Null returns everything (large for images and clouds; a message over 1 MiB is dropped with a note).
arrays_summary_onlyNoWhen true, arrays become a type-and-length summary: use it to read the non-array fields of large messages. Default false.

Output Schema

NameRequiredDescription
noteNoWhy `samples` is empty or limited, when not obvious; else `None`.
rateNoHow the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10.
countYesNumber of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50).
topicYes
samplesYesThe samples, in backend order.
mode_effectiveYes`live` or `mock` (fixtures).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only annotation, the description discloses QoS/latching behavior, timeout and truncation behavior, the 1 MiB cap, mock mode, MCP errors, lock contention and `busy` retry behavior, and total duration bounds. It never contradicts the annotations: it is consistently read-only and non-publishing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long and dense, but it is front-loaded with the graph/alternative caveat and then organized by mode, truncation, logging, summary/rate, and duration. Some parameter details duplicate the schema, which prevents a 5, but most sentences add useful operational context.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex sampling tool with timeout, truncation, QoS, logging, and lock behavior, the description covers what an agent needs to invoke it correctly. An output schema exists, so it reasonably omits full return-value documentation while still explaining key result fields like `payload`, `note`, `summary`, and `rate`.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already explains each parameter, but the description adds operational meaning: `count` is the actual number returned, latched topics usually need `count` 1, and `node`/`min_log_level` apply only to `/rosout`. It also explains the practical effect of `max_array_length` and `arrays_summary_only` on large messages.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: collect up to `count` live messages from a ROS 2 `topic` as a `SampleResult`. It immediately distinguishes the ROS 2 graph scope from DDS-only workflows by naming `list_endpoints` and `peek_dds_samples` as alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says this is for the ROS 2 graph only and routes DDS-only setups to `list_endpoints` or `peek_dds_samples`. It also distinguishes live versus mock mode and explains the `peek_bag_samples` ordering difference for log-style queries.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

topic_metricsA
Read-onlyIdempotent

Return temporal metrics (frequency, sequence gaps, latency percentiles) for a DDS topic over a recent window as a TopicMetrics. Read status first: unsupported_user_topic means the payload is not decoded, so no metric exists: every null or 0 is not a measurement; no_samples_yet is a builtin topic with nothing buffered; ok means computed. Limits: the buffer is filled only when peek_dds_samples runs on the topic, so observed_frequency_hz reflects how often it was called, not the publish rate: a coarse presence signal. declared_frequency_hz is 1 / deadline of the shortest writer Deadline announced in discovery (declared, not measured; null when none). Also sequence_gaps_count, latency_ns_p50/p95/p99 and availability flags. Read-only by architecture. Raises an MCP error when no DDS module is active or window_s is outside 1..3600. Right after start the call waits up to 3 s for discovery. Duration: DDS binding behind the DDS lock, one call at a time; ends within 45 s; busy error if the lock is held (retry).

NameRequiredDescriptionDefault
topicYesDDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only.
window_sNoWindow in seconds, 1..3600, default 60.
domain_idNoIgnored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value.

Output Schema

NameRequiredDescription
noteNoOne sentence of context, or `None`.
topicYes
statusNo`ok`; `no_samples_yet` (empty buffer); `unsupported_user_topic` (payload not decoded: null fields are not measurements).
window_sYesRequested window in seconds (echoed).
latency_ns_p50NoMedian publish-to-receive latency in ns; `None` unless `latency_available`.
latency_ns_p95No95th-percentile publish-to-receive latency (ns).
latency_ns_p99No99th-percentile publish-to-receive latency (ns).
mode_effectiveYes`live` or `mock` (fixtures).
window_actual_sYesSeconds actually covered; shorter than `window_s` after a recent start; `0.0` without samples.
samples_observedYesSamples buffered in the window. `0` does NOT mean no publisher, only that no `peek_dds_samples` call captured one.
latency_availableNoTrue when a sample carried both a publish and a receive timestamp.
sequence_gaps_countNoMissing sequence numbers in the buffered samples; `0` also when none exist (see `sequence_numbers_available`).
declared_frequency_hzNo`1 / deadline` of the shortest writer Deadline announced: the promised rate, not the observed one; `None` when none announced.
observed_frequency_hzNo`samples_observed / window_actual_s`; `None` under 2 samples.
sequence_numbers_availableNoTrue when a sequence number could be read from a sample.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only annotations, the description explains status values, what null/zero values mean, the distinction between observed and declared frequency, error conditions, discovery wait, locking, duration, and busy retry behavior. This is unusually rich behavioral context for an agent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but well front-loaded: purpose first, then critical status semantics, then limits, errors, and concurrency. Despite its length, every section provides operationally useful information with no evident filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, existing output schema, and annotations, the description covers the critical gaps an agent needs: status interpretation, buffer limitations, domain behavior, errors, and timing. Nothing essential for correct invocation appears missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents topic, window_s, and domain_id semantics thoroughly. The description reinforces window bounds and domain_id behavior but adds little parameter meaning beyond the structured schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific operation and resource: return temporal metrics for a DDS topic, naming frequency, sequence gaps, and latency percentiles. It clearly distinguishes the tool as a metrics-computation endpoint rather than a raw sample reader like peek_dds_samples.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear operational guidance: read status first, and understand that the buffer is populated only when peek_dds_samples runs on the topic. It does not explicitly compare this tool against all sibling alternatives, so it stops short of a full when-to-use/when-not matrix.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 16 tool updatesv0.8.0
    • Changedanalyze_bag31 fields changed
      • removedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / default
        Removed value: -null
      • addedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / BagTopicStats / properties / frequency_basis / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "topic_span",
        -      "bag_duration"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / BagTopicStats / properties / frequency_basis / default
        Removed value: -null
      • addedOutput schema / $defs / BagTopicStats / properties / frequency_basis / enum
        Added value: +[
        +  "topic_span",
        +  "bag_duration",
        +  null
        +]
      • addedOutput schema / $defs / BagTopicStats / properties / frequency_basis / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / BagTopicStats / properties / frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / BagTopicStats / properties / frequency_hz / default
        Removed value: -null
      • addedOutput schema / $defs / BagTopicStats / properties / frequency_hz / minimum
        Added value: +0
      • addedOutput schema / $defs / BagTopicStats / properties / frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / BagTopicStats / properties / kind / default
        Removed value: -"user"
      • removedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / default
        Removed value: -null
      • addedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / BagTopicStats / properties / latched / anyOf
        Removed value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / BagTopicStats / properties / latched / default
        Removed value: -null
      • addedOutput schema / $defs / BagTopicStats / properties / latched / type
        Added value: +[
        +  "boolean",
        +  "null"
        +]
      • removedOutput schema / properties / bag_format / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "mcap",
        -      "db3",
        -      "bag",
        -      "unknown"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / bag_format / default
        Removed value: -null
      • addedOutput schema / properties / bag_format / enum
        Added value: +[
        +  "mcap",
        +  "db3",
        +  "bag",
        +  "unknown",
        +  null
        +]
      • addedOutput schema / properties / bag_format / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • addedOutput schema / properties / start_ns
        Added value: +{
        +  "description": "Record time in ns of the bag's first message (rosbag2 `starting_time`): the zero of `peek_bag_samples` `start_s` / `end_s`. `null` when unreadable.",
        +  "type": [
        +    "integer",
        +    "null"
        +  ]
        +}
      • removedOutput schema / properties / storage_format / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / storage_format / default
        Removed value: -null
      • addedOutput schema / properties / storage_format / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changeddetect_qos_mismatches53 fields changed
      • removedOutput schema / $defs / MatchedPair / properties / late_joiner / default
        Removed value: -false
      • removedOutput schema / $defs / MatchedPair / properties / late_joiner_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MatchedPair / properties / late_joiner_note / default
        Removed value: -null
      • addedOutput schema / $defs / MatchedPair / properties / late_joiner_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MatchedPair / properties / reader_participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MatchedPair / properties / reader_participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / MatchedPair / properties / reader_participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MatchedPair / properties / type_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MatchedPair / properties / type_name / default
        Removed value: -null
      • addedOutput schema / $defs / MatchedPair / properties / type_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MatchedPair / properties / writer_participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MatchedPair / properties / writer_participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / MatchedPair / properties / writer_participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / reader_guid / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / reader_guid / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / reader_guid / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_guid / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_guid / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / reader_participant_guid / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / reader_participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / reader_type_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / reader_type_name / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / reader_type_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / writer_guid / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / writer_guid / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / writer_guid / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_guid / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_guid / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / writer_participant_guid / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / writer_participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / MismatchReport / properties / writer_type_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MismatchReport / properties / writer_type_name / default
        Removed value: -null
      • addedOutput schema / $defs / MismatchReport / properties / writer_type_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / NotMatchedPair / properties / reader_participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / NotMatchedPair / properties / writer_participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / matched_total / default
        Removed value: -0
      • removedOutput schema / properties / not_matched_total / default
        Removed value: -0
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / reports_total / default
        Removed value: -0
      • removedOutput schema / properties / summary / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / summary / default
        Removed value: -null
      • addedOutput schema / properties / summary / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / truncated / default
        Removed value: -false
    • Changedget_node_info25 fields changed
      • addedInput schema / properties / params_file
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Server path of a ROS 2 parameter `.yaml` (1 MiB max)."
        +}
      • removedOutput schema / $defs / NodeParameter / properties / original_size / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / NodeParameter / properties / original_size / default
        Removed value: -null
      • addedOutput schema / $defs / NodeParameter / properties / original_size / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • addedOutput schema / $defs / ParamDiff
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "declared": {
        +      "$ref": "#/$defs/JsonValue"
        +    },
        +    "live": {
        +      "$ref": "#/$defs/JsonValue"
        +    },
        +    "name": {
        +      "type": "string"
        +    },
        +    "status": {
        +      "description": "`declared_only`: absent on the node.",
        +      "enum": [
        +        "different",
        +        "declared_only",
        +        "masked"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "name",
        +    "status",
        +    "declared",
        +    "live"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / lifecycle
        Added value: +{
        +  "description": "True when `service_servers` has `<full_name>/get_state` (GetState).",
        +  "type": "boolean"
        +}
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • addedOutput schema / properties / param_diff
        Added value: +{
        +  "description": "File against node: differences only.",
        +  "items": {
        +    "$ref": "#/$defs/ParamDiff"
        +  },
        +  "type": [
        +    "array",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / param_diff_note
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / param_diff_summary
        Added value: +{
        +  "description": "Counts left out: same, live_only.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • removedOutput schema / properties / parameters / anyOf
        Removed value: -[
        -  {
        -    "items": {
        -      "$ref": "#/$defs/NodeParameter"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / parameters / default
        Removed value: -null
      • addedOutput schema / properties / parameters / items
        Added value: +{
        +  "$ref": "#/$defs/NodeParameter"
        +}
      • addedOutput schema / properties / parameters / type
        Added value: +[
        +  "array",
        +  "null"
        +]
      • removedOutput schema / properties / parameters_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / parameters_note / default
        Removed value: -null
      • addedOutput schema / properties / parameters_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / use_sim_time / anyOf
        Removed value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / use_sim_time / default
        Removed value: -null
      • addedOutput schema / properties / use_sim_time / type
        Added value: +[
        +  "boolean",
        +  "null"
        +]
      • removedOutput schema / properties / use_sim_time_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / use_sim_time_note / default
        Removed value: -null
      • addedOutput schema / properties / use_sim_time_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedget_topic_info11 fields changed
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / publisher_qos / default
        Removed value: -null
      • removedOutput schema / properties / publisher_qos_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / publisher_qos_note / default
        Removed value: -null
      • addedOutput schema / properties / publisher_qos_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / subscription_qos / default
        Removed value: -null
      • removedOutput schema / properties / subscription_qos_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / subscription_qos_note / default
        Removed value: -null
      • addedOutput schema / properties / subscription_qos_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Addedgraph_diff
    • Changedhealth_check70 fields changed
      • removedOutput schema / properties / contract_version / default
        Removed value: -2
      • changedOutput schema / properties / contract_version / description
        Previous value: -"Output contract version; changes only when a field is renamed, removed or retyped."New value: +"Output contract version; changes when a field is renamed, removed or retyped."
      • removedOutput schema / properties / dds_backend / default
        Removed value: -"none"
      • removedOutput schema / properties / dds_domain_id / anyOf
        Removed value: -[
        -  {
        -    "maximum": 232,
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / dds_domain_id / default
        Removed value: -null
      • addedOutput schema / properties / dds_domain_id / maximum
        Added value: +232
      • addedOutput schema / properties / dds_domain_id / minimum
        Added value: +0
      • addedOutput schema / properties / dds_domain_id / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / dds_inactive_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / dds_inactive_note / default
        Removed value: -null
      • changedOutput schema / properties / dds_inactive_note / description
        Previous value: -"Why `dds_backend` is `none` while ROS 2 serves (not selected, binding missing, or failed to start); else `null`."New value: +"Why `dds_backend` is `none` while ROS 2 serves; else `null`."
      • addedOutput schema / properties / dds_inactive_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / dds_security / default
        Removed value: -"not_supported"
      • removedOutput schema / properties / middleware_available / default
        Removed value: -false
      • changedOutput schema / properties / mode / description
        Previous value: -"Mode actually serving: `mock` or `live` (`live` with `ros_backend` `none`: DDS tools only). May differ from `requested_mode` after a fallback."New value: +"Mode serving: `mock` or `live` (`live` with `ros_backend` `none`: DDS tools only); may differ from `requested_mode` after a fallback."
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / now_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / now_ns / default
        Removed value: -null
      • addedOutput schema / properties / now_ns / minimum
        Added value: +0
      • addedOutput schema / properties / now_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / observed_domain_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / observed_domain_note / default
        Removed value: -null
      • addedOutput schema / properties / observed_domain_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / observer_started_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / observer_started_ns / default
        Removed value: -null
      • addedOutput schema / properties / observer_started_ns / minimum
        Added value: +0
      • addedOutput schema / properties / observer_started_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / payload_decoding / default
        Removed value: -"disabled"
      • removedOutput schema / properties / payload_decoding_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / payload_decoding_note / default
        Removed value: -"user-topic payload decoding is switched off until it is validated on a real bus; builtin discovery topics are still readable"
      • addedOutput schema / properties / payload_decoding_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rmw_implementation / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / rmw_implementation / default
        Removed value: -null
      • addedOutput schema / properties / rmw_implementation / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rmw_source / default
        Removed value: -"none"
      • removedOutput schema / properties / ros2_distro / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / ros2_distro / default
        Removed value: -null
      • addedOutput schema / properties / ros2_distro / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / ros_backend / default
        Removed value: -"none"
      • addedOutput schema / properties / ros_security_enabled
        Added value: +{
        +  "description": "`ROS_SECURITY_ENABLE` is `true`; `None` unless the ros2 CLI serves.",
        +  "type": [
        +    "boolean",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / ros_security_keystore_set
        Added value: +{
        +  "description": "Whether a keystore variable is set (the path is never shown).",
        +  "type": [
        +    "boolean",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / ros_security_note
        Added value: +{
        +  "description": "Set when `ros_security_enabled`: what stays invisible.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / ros_security_strategy
        Added value: +{
        +  "description": "`ROS_SECURITY_STRATEGY` when set to a valid value.",
        +  "enum": [
        +    "Enforce",
        +    "Permissive",
        +    null
        +  ],
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • removedOutput schema / properties / ros_tools_available / default
        Removed value: -false
      • removedOutput schema / properties / sim_clock_published / anyOf
        Removed value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / sim_clock_published / default
        Removed value: -null
      • changedOutput schema / properties / sim_clock_published / description
        Previous value: -"Whether `/clock` had a publisher in the last graph read (a hint that stamps may be sim time). `None` in mock or without a recent read; `health_check` never runs `ros2`."New value: +"Whether `/clock` had a publisher at the last graph read (stamps may be sim time); `None` in mock or without a recent read."
      • addedOutput schema / properties / sim_clock_published / type
        Added value: +[
        +  "boolean",
        +  "null"
        +]
      • removedOutput schema / properties / tracker_cache_evictions / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / tracker_cache_evictions / default
        Removed value: -null
      • changedOutput schema / properties / tracker_cache_evictions / description
        Previous value: -"Discovery entries dropped from a full tracker cache (4096 each); non-zero means the bus is bigger than listed."New value: +"Discovery entries dropped from a full tracker cache; non-zero means the bus is bigger than listed."
      • addedOutput schema / properties / tracker_cache_evictions / minimum
        Added value: +0
      • addedOutput schema / properties / tracker_cache_evictions / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / tracker_errors / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / tracker_errors / default
        Removed value: -null
      • addedOutput schema / properties / tracker_errors / minimum
        Added value: +0
      • addedOutput schema / properties / tracker_errors / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / tracker_last_pass_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / tracker_last_pass_ns / default
        Removed value: -null
      • addedOutput schema / properties / tracker_last_pass_ns / minimum
        Added value: +0
      • addedOutput schema / properties / tracker_last_pass_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / tracker_passes / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / tracker_passes / default
        Removed value: -null
      • addedOutput schema / properties / tracker_passes / minimum
        Added value: +0
      • addedOutput schema / properties / tracker_passes / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / tracker_running / anyOf
        Removed value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / tracker_running / default
        Removed value: -null
      • addedOutput schema / properties / tracker_running / type
        Added value: +[
        +  "boolean",
        +  "null"
        +]
    • Addedinspect_tf
    • Changedlist_endpoints103 fields changed
      • removedOutput schema / $defs / DepartedEndpoint / properties / gone_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / DepartedEndpoint / properties / gone_ns / default
        Removed value: -null
      • addedOutput schema / $defs / DepartedEndpoint / properties / gone_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / DepartedEndpoint / properties / participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / DepartedEndpoint / properties / participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / DepartedEndpoint / properties / participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / EndpointInfo / properties / announced_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / announced_ns / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / announced_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / EndpointInfo / properties / gone_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / gone_ns / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / gone_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • addedOutput schema / $defs / EndpointInfo / properties / node_name
        Added value: +{
        +  "description": "ROS 2 node owning the endpoint (`ros_discovery_info`); `None` if not announced.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • removedOutput schema / $defs / EndpointInfo / properties / participant_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / participant_name / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / participant_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / EndpointInfo / properties / participant_vendor / default
        Removed value: -"unknown"
      • removedOutput schema / $defs / EndpointInfo / properties / qos / default
        Removed value: -null
      • removedOutput schema / $defs / EndpointInfo / properties / qos_summary / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / qos_summary / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / qos_summary / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / EndpointInfo / properties / ros_topic / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / ros_topic / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / ros_topic / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / EndpointInfo / properties / ros_topic_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / ros_topic_note / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / ros_topic_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / EndpointInfo / properties / type_id / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / type_id / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / type_id / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / EndpointInfo / properties / type_name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / EndpointInfo / properties / type_name / default
        Removed value: -null
      • addedOutput schema / $defs / EndpointInfo / properties / type_name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / data_representation / anyOf
        Removed value: -[
        -  {
        -    "items": {
        -      "type": "string"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / data_representation / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / data_representation / items
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / $defs / QosProfile / properties / data_representation / type
        Added value: +[
        +  "array",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / deadline_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / deadline_ns / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / deadline_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / QosProfile / properties / deadline_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / destination_order / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "BY_RECEPTION_TIMESTAMP",
        -      "BY_SOURCE_TIMESTAMP"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / destination_order / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / destination_order / enum
        Added value: +[
        +  "BY_RECEPTION_TIMESTAMP",
        +  "BY_SOURCE_TIMESTAMP",
        +  null
        +]
      • addedOutput schema / $defs / QosProfile / properties / destination_order / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / history / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "KEEP_LAST",
        -      "KEEP_ALL"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / history / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / history / enum
        Added value: +[
        +  "KEEP_LAST",
        +  "KEEP_ALL",
        +  null
        +]
      • addedOutput schema / $defs / QosProfile / properties / history / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / history_depth / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / history_depth / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / history_depth / minimum
        Added value: +0
      • addedOutput schema / $defs / QosProfile / properties / history_depth / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / history_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / history_note / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / history_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / latency_budget_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / latency_budget_ns / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / latency_budget_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / QosProfile / properties / latency_budget_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / liveliness_kind / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "AUTOMATIC",
        -      "MANUAL_BY_PARTICIPANT",
        -      "MANUAL_BY_TOPIC"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / liveliness_kind / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / liveliness_kind / enum
        Added value: +[
        +  "AUTOMATIC",
        +  "MANUAL_BY_PARTICIPANT",
        +  "MANUAL_BY_TOPIC",
        +  null
        +]
      • addedOutput schema / $defs / QosProfile / properties / liveliness_kind / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / liveliness_lease_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / liveliness_lease_ns / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / liveliness_lease_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / QosProfile / properties / liveliness_lease_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / ownership_kind / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "SHARED",
        -      "EXCLUSIVE"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / ownership_kind / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / ownership_kind / enum
        Added value: +[
        +  "SHARED",
        +  "EXCLUSIVE",
        +  null
        +]
      • addedOutput schema / $defs / QosProfile / properties / ownership_kind / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / ownership_strength / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / ownership_strength / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / ownership_strength / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / partitions / anyOf
        Removed value: -[
        -  {
        -    "items": {
        -      "type": "string"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / partitions / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / partitions / items
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / $defs / QosProfile / properties / partitions / type
        Added value: +[
        +  "array",
        +  "null"
        +]
      • removedOutput schema / $defs / QosProfile / properties / unknown_policies / anyOf
        Removed value: -[
        -  {
        -    "items": {
        -      "type": "string"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / QosProfile / properties / unknown_policies / default
        Removed value: -null
      • addedOutput schema / $defs / QosProfile / properties / unknown_policies / items
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / $defs / QosProfile / properties / unknown_policies / type
        Added value: +[
        +  "array",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicSummary / properties / orphan / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "no_reader",
        -      "no_writer"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / TopicSummary / properties / orphan / default
        Removed value: -null
      • addedOutput schema / $defs / TopicSummary / properties / orphan / enum
        Added value: +[
        +  "no_reader",
        +  "no_writer",
        +  null
        +]
      • addedOutput schema / $defs / TopicSummary / properties / orphan / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • addedOutput schema / $defs / TopicSummary / properties / reader_nodes
        Added value: +{
        +  "description": "Sorted ROS 2 nodes of the readers (`node_name`).",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • removedOutput schema / $defs / TopicSummary / properties / ros_topic / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / TopicSummary / properties / ros_topic / default
        Removed value: -null
      • addedOutput schema / $defs / TopicSummary / properties / ros_topic / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • addedOutput schema / $defs / TopicSummary / properties / writer_nodes
        Added value: +{
        +  "description": "Sorted ROS 2 nodes of the writers (`node_name`).",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • removedOutput schema / description
        Removed value: -"Envelope returned by `list_endpoints`."
      • removedOutput schema / properties / departed_endpoint_count / default
        Removed value: -0
      • removedOutput schema / properties / excluded_observer_endpoint_count / default
        Removed value: -0
      • removedOutput schema / properties / fields / default
        Removed value: -"full"
      • removedOutput schema / properties / hidden_internal_endpoint_count / default
        Removed value: -0
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / observer_guid / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / observer_guid / default
        Removed value: -null
      • addedOutput schema / properties / observer_guid / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedlist_nodes8 fields changed
      • addedOutput schema / $defs / NodeListItem / properties / lifecycle
        Added value: +{
        +  "description": "True when the graph has `<full_name>/get_state` of type `lifecycle_msgs/srv/GetState` (a managed node). The state itself is never read.",
        +  "type": "boolean"
        +}
      • removedOutput schema / description
        Removed value: -"Envelope returned by `list_nodes`."
      • addedOutput schema / properties / by_namespace
        Added value: +{
        +  "additionalProperties": {
        +    "type": "integer"
        +  },
        +  "description": "Distinct full names per namespace (`/` is the root), sorted by namespace.",
        +  "type": "object"
        +}
      • addedOutput schema / properties / lifecycle_count
        Added value: +{
        +  "description": "Nodes with `lifecycle` true.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / lifecycle_note
        Added value: +{
        +  "description": "Why `lifecycle` could not be determined (service list failed), else `None`.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedlist_participants41 fields changed
      • removedOutput schema / $defs / ParticipantInfo / properties / announced_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantInfo / properties / announced_ns / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantInfo / properties / announced_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / ParticipantInfo / properties / announced_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • addedOutput schema / $defs / ParticipantInfo / properties / endpoint_count
        Added value: +{
        +  "description": "Writers plus readers seen for this participant; `None` without an endpoint cache.",
        +  "minimum": 0,
        +  "type": [
        +    "integer",
        +    "null"
        +  ]
        +}
      • removedOutput schema / $defs / ParticipantInfo / properties / first_seen_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantInfo / properties / first_seen_ns / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantInfo / properties / first_seen_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / ParticipantInfo / properties / first_seen_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantInfo / properties / hostname / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantInfo / properties / hostname / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantInfo / properties / hostname / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantInfo / properties / is_observer / default
        Removed value: -false
      • addedOutput schema / $defs / ParticipantInfo / properties / is_topicforge_cli
        Added value: +{
        +  "description": "True for a `ros2` CLI process TopicForge spawned (lingers ~2 s); not the robot.",
        +  "type": "boolean"
        +}
      • removedOutput schema / $defs / ParticipantInfo / properties / last_seen_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantInfo / properties / last_seen_ns / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantInfo / properties / last_seen_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / ParticipantInfo / properties / last_seen_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantInfo / properties / lost_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantInfo / properties / lost_ns / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantInfo / properties / lost_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / ParticipantInfo / properties / lost_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantInfo / properties / lost_time_source / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "dds_source_timestamp",
        -      "observed_local"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantInfo / properties / lost_time_source / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantInfo / properties / lost_time_source / enum
        Added value: +[
        +  "dds_source_timestamp",
        +  "observed_local",
        +  null
        +]
      • addedOutput schema / $defs / ParticipantInfo / properties / lost_time_source / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantInfo / properties / name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantInfo / properties / name / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantInfo / properties / name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantInfo / properties / node_names_source / default
        Removed value: -"none"
      • removedOutput schema / $defs / ParticipantInfo / properties / seen_count / default
        Removed value: -1
      • removedOutput schema / $defs / ParticipantInfo / properties / status / default
        Removed value: -"unknown"
      • removedOutput schema / $defs / ParticipantInfo / properties / vendor_source / default
        Removed value: -"none"
      • removedOutput schema / description
        Removed value: -"Envelope returned by `list_participants`."
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • addedOutput schema / properties / restarts_by_node
        Added value: +{
        +  "additionalProperties": {
        +    "$ref": "#/$defs/NameLifecycle"
        +  },
        +  "description": "`restarts_by_name` keyed by ROS 2 node full name.",
        +  "type": "object"
        +}
      • removedOutput schema / properties / restarts_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / restarts_note / default
        Removed value: -null
      • addedOutput schema / properties / restarts_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedlist_topics4 fields changed
      • removedOutput schema / description
        Removed value: -"Envelope returned by `list_topics`."
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedparticipant_events22 fields changed
      • removedOutput schema / $defs / ParticipantEvent / properties / age_s / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantEvent / properties / age_s / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantEvent / properties / age_s / minimum
        Added value: +0
      • addedOutput schema / $defs / ParticipantEvent / properties / age_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantEvent / properties / hostname / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantEvent / properties / hostname / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantEvent / properties / hostname / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantEvent / properties / name / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantEvent / properties / name / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantEvent / properties / name / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantEvent / properties / observed_ns / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / ParticipantEvent / properties / observed_ns / default
        Removed value: -null
      • addedOutput schema / $defs / ParticipantEvent / properties / observed_ns / minimum
        Added value: +0
      • addedOutput schema / $defs / ParticipantEvent / properties / observed_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / ParticipantEvent / properties / time_source / default
        Removed value: -"observed_local"
      • removedOutput schema / description
        Removed value: -"Envelope returned by `participant_events`."
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / restarts_note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / restarts_note / default
        Removed value: -null
      • addedOutput schema / properties / restarts_note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpeek_bag_samples46 fields changed
      • addedInput schema / properties / end_s
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minimum": 0,
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Only messages recorded before this bag-relative second (exclusive), default to the end."
        +}
      • addedInput schema / properties / min_log_level
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "debug",
        +        "info",
        +        "warn",
        +        "error",
        +        "fatal"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "`/rosout` (`rcl_interfaces/msg/Log`) only: keep messages at or above this level (debug 10, info 20, warn 30, error 40, fatal 50). Other topics: error."
        +}
      • addedInput schema / properties / node
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "`/rosout` only: keep logs of this node and its child loggers, e.g. `/robot1/driver` (also `driver`, `robot1.driver`). Exact name, never a prefix. Other topics: error."
        +}
      • addedInput schema / properties / start_s
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minimum": 0,
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Only messages recorded at or after this many seconds from the bag's first message (`analyze_bag.start_ns`), default from the start."
        +}
      • removedOutput schema / $defs / ImageSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "data_length": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixel buffer size in bytes (see `data_length_basis`)."
        -    },
        -    "data_length_basis": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "payload",
        -            "step_x_height"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)."
        -    },
        -    "encoding": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixel encoding, e.g. `rgb8`."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the camera."
        -    },
        -    "height": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixels."
        -    },
        -    "is_bigendian": {
        -      "anyOf": [
        -        {
        -          "type": "boolean"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Big-endian pixels."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when partial; else `null`."
        -    },
        -    "step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Row length, bytes."
        -    },
        -    "summary_type": {
        -      "const": "image",
        -      "type": "string"
        -    },
        -    "width": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixels."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "width",
        -    "height",
        -    "encoding",
        -    "step",
        -    "is_bigendian",
        -    "data_length",
        -    "data_length_basis"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ImuSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angular_velocity_norm": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `angular_velocity`, rad/s."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the IMU."
        -    },
        -    "linear_acceleration_norm": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `linear_acceleration`, m/s^2."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when a value is unusable; else `null`."
        -    },
        -    "orientation_valid": {
        -      "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.",
        -      "type": "boolean"
        -    },
        -    "pitch": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about y, radians."
        -    },
        -    "roll": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about x, radians."
        -    },
        -    "summary_type": {
        -      "const": "imu",
        -      "type": "string"
        -    },
        -    "yaw": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about z, radians."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "orientation_valid",
        -    "roll",
        -    "pitch",
        -    "yaw",
        -    "angular_velocity_norm",
        -    "linear_acceleration_norm"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / LaserScanSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angle_increment": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Angle between beams, rad."
        -    },
        -    "angle_max": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bearing of the last beam, rad."
        -    },
        -    "angle_min": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bearing of beam 0, rad."
        -    },
        -    "beam_count": {
        -      "description": "Beams in `ranges`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "closest_obstacle": {
        -      "anyOf": [
        -        {
        -          "$ref": "#/$defs/ScanReturn"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Closest valid return of the scan; `null` with `note`."
        -    },
        -    "finite_count": {
        -      "description": "Beams with a finite range.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame."
        -    },
        -    "inf_count": {
        -      "description": "Beams at `+inf`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "nan_count": {
        -      "description": "Beams at `nan`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "neg_inf_count": {
        -      "description": "Beams at `-inf`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when the summary is partial; else `null`."
        -    },
        -    "out_of_range_count": {
        -      "description": "Finite ranges outside `range_min`..`range_max` (not valid).",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "range_max": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Largest valid range, m."
        -    },
        -    "range_min": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Smallest valid range, m."
        -    },
        -    "sectors": {
        -      "$ref": "#/$defs/ScanSectors",
        -      "description": "Closest valid return per sector."
        -    },
        -    "summary_type": {
        -      "const": "laser_scan",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "beam_count",
        -    "angle_min",
        -    "angle_max",
        -    "angle_increment",
        -    "range_min",
        -    "range_max",
        -    "finite_count",
        -    "inf_count",
        -    "neg_inf_count",
        -    "nan_count",
        -    "out_of_range_count",
        -    "closest_obstacle",
        -    "sectors"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / MessageSample / properties / received_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / received_ns / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / received_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / recorded_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / MessageSample / properties / summary / anyOf
        Removed value: -[
        -  {
        -    "discriminator": {
        -      "mapping": {
        -        "image": "#/$defs/ImageSummary",
        -        "imu": "#/$defs/ImuSummary",
        -        "laser_scan": "#/$defs/LaserScanSummary",
        -        "odometry": "#/$defs/OdometrySummary",
        -        "point_cloud2": "#/$defs/PointCloud2Summary"
        -      },
        -      "propertyName": "summary_type"
        -    },
        -    "oneOf": [
        -      {
        -        "$ref": "#/$defs/LaserScanSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/OdometrySummary"
        -      },
        -      {
        -        "$ref": "#/$defs/ImuSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/ImageSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/PointCloud2Summary"
        -      }
        -    ]
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / summary / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / summary / properties
        Added value: +{
        +  "summary_type": {
        +    "enum": [
        +      "laser_scan",
        +      "odometry",
        +      "imu",
        +      "image",
        +      "point_cloud2"
        +    ],
        +    "type": "string"
        +  }
        +}
      • addedOutput schema / $defs / MessageSample / properties / summary / required
        Added value: +[
        +  "summary_type"
        +]
      • addedOutput schema / $defs / MessageSample / properties / summary / type
        Added value: +[
        +  "object",
        +  "null"
        +]
      • removedOutput schema / $defs / OdometrySummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angular_z": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`twist.twist.angular.z`, rad/s."
        -    },
        -    "child_frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`child_frame_id` (frame of the twist)."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` (frame of the pose)."
        -    },
        -    "linear_speed": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `twist.twist.linear`, m/s."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when a value is missing; else `null`."
        -    },
        -    "position_x": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.x`, m."
        -    },
        -    "position_y": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.y`, m."
        -    },
        -    "position_z": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.z`, m."
        -    },
        -    "summary_type": {
        -      "const": "odometry",
        -      "type": "string"
        -    },
        -    "yaw": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Heading from the pose quaternion, radians."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "child_frame_id",
        -    "linear_speed",
        -    "angular_z",
        -    "position_x",
        -    "position_y",
        -    "position_z",
        -    "yaw"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / PointCloud2Summary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "fields": {
        -      "description": "Point layout.",
        -      "items": {
        -        "$ref": "#/$defs/PointFieldSummary"
        -      },
        -      "type": "array"
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the cloud."
        -    },
        -    "height": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rows (1: unorganized)."
        -    },
        -    "is_dense": {
        -      "anyOf": [
        -        {
        -          "type": "boolean"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "True when no point is invalid."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when partial; else `null`."
        -    },
        -    "point_count": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`width` times `height`."
        -    },
        -    "point_step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bytes per point."
        -    },
        -    "row_step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bytes per row."
        -    },
        -    "summary_type": {
        -      "const": "point_cloud2",
        -      "type": "string"
        -    },
        -    "width": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Points per row."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "width",
        -    "height",
        -    "point_count",
        -    "point_step",
        -    "row_step",
        -    "is_dense",
        -    "fields"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / PointFieldSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "count": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Elements per point."
        -    },
        -    "datatype": {
        -      "description": "Element type, e.g. `FLOAT32`.",
        -      "type": "string"
        -    },
        -    "name": {
        -      "description": "Field name, e.g. `x`.",
        -      "type": "string"
        -    },
        -    "offset": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Byte offset in a point."
        -    }
        -  },
        -  "required": [
        -    "name",
        -    "datatype",
        -    "offset",
        -    "count"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanReturn
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "beam_index": {
        -      "description": "Index in `ranges`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "bearing": {
        -      "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.",
        -      "type": "number"
        -    },
        -    "range": {
        -      "description": "Distance, meters.",
        -      "type": "number"
        -    }
        -  },
        -  "required": [
        -    "range",
        -    "bearing",
        -    "beam_index"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanSector
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "beam_count": {
        -      "description": "Beams inside the sector (0: not covered).",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "closest": {
        -      "anyOf": [
        -        {
        -          "$ref": "#/$defs/ScanReturn"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Closest valid return in the sector; `null` with `note`."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Why `closest` is `null`; `null` when it is set."
        -    }
        -  },
        -  "required": [
        -    "beam_count",
        -    "closest"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanSectors
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "front": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings within +-45 degrees of +x."
        -    },
        -    "left": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings from 45 to 135 degrees."
        -    },
        -    "rear": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings beyond +-135 degrees."
        -    },
        -    "right": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings from -135 to -45 degrees."
        -    }
        -  },
        -  "required": [
        -    "front",
        -    "left",
        -    "right",
        -    "rear"
        -  ],
        -  "type": "object"
        -}
      • addedOutput schema / $defs / TopicRate / properties / clock_basis
        Added value: +{
        +  "description": "Stamp clock: `wall` (stamps are dates), `sim` (since a start), `none` (no stamps), `mixed` (several kinds, e.g. two publishers).",
        +  "enum": [
        +    "wall",
        +    "sim",
        +    "none",
        +    "mixed"
        +  ],
        +  "type": "string"
        +}
      • removedOutput schema / $defs / TopicRate / properties / interval_cv / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / interval_cv / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / interval_median_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / interval_median_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / max_gap_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / max_gap_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / mean_interval_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / mean_interval_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / observed_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / observed_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / sim_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / sim_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • addedOutput schema / $defs / TopicRate / properties / stamp_age_median_s
        Added value: +{
        +  "description": "Median of arrival minus stamp, s, if `wall`; negative = publisher ahead.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / $defs / TopicRate / properties / stamp_age_p95_s
        Added value: +{
        +  "description": "95th percentile of that age (max under 20 messages).",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / $defs / TopicRate / properties / stamp_regression_count
        Added value: +{
        +  "description": "Times a stamp went back versus the previous one.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • removedOutput schema / $defs / TopicRate / properties / trailing_gap_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / trailing_gap_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rate / default
        Removed value: -null
    • Changedpeek_dds_samples42 fields changed
      • removedOutput schema / $defs / ImageSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "data_length": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixel buffer size in bytes (see `data_length_basis`)."
        -    },
        -    "data_length_basis": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "payload",
        -            "step_x_height"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)."
        -    },
        -    "encoding": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixel encoding, e.g. `rgb8`."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the camera."
        -    },
        -    "height": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixels."
        -    },
        -    "is_bigendian": {
        -      "anyOf": [
        -        {
        -          "type": "boolean"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Big-endian pixels."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when partial; else `null`."
        -    },
        -    "step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Row length, bytes."
        -    },
        -    "summary_type": {
        -      "const": "image",
        -      "type": "string"
        -    },
        -    "width": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixels."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "width",
        -    "height",
        -    "encoding",
        -    "step",
        -    "is_bigendian",
        -    "data_length",
        -    "data_length_basis"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ImuSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angular_velocity_norm": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `angular_velocity`, rad/s."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the IMU."
        -    },
        -    "linear_acceleration_norm": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `linear_acceleration`, m/s^2."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when a value is unusable; else `null`."
        -    },
        -    "orientation_valid": {
        -      "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.",
        -      "type": "boolean"
        -    },
        -    "pitch": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about y, radians."
        -    },
        -    "roll": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about x, radians."
        -    },
        -    "summary_type": {
        -      "const": "imu",
        -      "type": "string"
        -    },
        -    "yaw": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about z, radians."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "orientation_valid",
        -    "roll",
        -    "pitch",
        -    "yaw",
        -    "angular_velocity_norm",
        -    "linear_acceleration_norm"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / LaserScanSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angle_increment": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Angle between beams, rad."
        -    },
        -    "angle_max": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bearing of the last beam, rad."
        -    },
        -    "angle_min": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bearing of beam 0, rad."
        -    },
        -    "beam_count": {
        -      "description": "Beams in `ranges`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "closest_obstacle": {
        -      "anyOf": [
        -        {
        -          "$ref": "#/$defs/ScanReturn"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Closest valid return of the scan; `null` with `note`."
        -    },
        -    "finite_count": {
        -      "description": "Beams with a finite range.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame."
        -    },
        -    "inf_count": {
        -      "description": "Beams at `+inf`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "nan_count": {
        -      "description": "Beams at `nan`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "neg_inf_count": {
        -      "description": "Beams at `-inf`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when the summary is partial; else `null`."
        -    },
        -    "out_of_range_count": {
        -      "description": "Finite ranges outside `range_min`..`range_max` (not valid).",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "range_max": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Largest valid range, m."
        -    },
        -    "range_min": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Smallest valid range, m."
        -    },
        -    "sectors": {
        -      "$ref": "#/$defs/ScanSectors",
        -      "description": "Closest valid return per sector."
        -    },
        -    "summary_type": {
        -      "const": "laser_scan",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "beam_count",
        -    "angle_min",
        -    "angle_max",
        -    "angle_increment",
        -    "range_min",
        -    "range_max",
        -    "finite_count",
        -    "inf_count",
        -    "neg_inf_count",
        -    "nan_count",
        -    "out_of_range_count",
        -    "closest_obstacle",
        -    "sectors"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / MessageSample / properties / received_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / received_ns / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / received_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / recorded_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / MessageSample / properties / summary / anyOf
        Removed value: -[
        -  {
        -    "discriminator": {
        -      "mapping": {
        -        "image": "#/$defs/ImageSummary",
        -        "imu": "#/$defs/ImuSummary",
        -        "laser_scan": "#/$defs/LaserScanSummary",
        -        "odometry": "#/$defs/OdometrySummary",
        -        "point_cloud2": "#/$defs/PointCloud2Summary"
        -      },
        -      "propertyName": "summary_type"
        -    },
        -    "oneOf": [
        -      {
        -        "$ref": "#/$defs/LaserScanSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/OdometrySummary"
        -      },
        -      {
        -        "$ref": "#/$defs/ImuSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/ImageSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/PointCloud2Summary"
        -      }
        -    ]
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / summary / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / summary / properties
        Added value: +{
        +  "summary_type": {
        +    "enum": [
        +      "laser_scan",
        +      "odometry",
        +      "imu",
        +      "image",
        +      "point_cloud2"
        +    ],
        +    "type": "string"
        +  }
        +}
      • addedOutput schema / $defs / MessageSample / properties / summary / required
        Added value: +[
        +  "summary_type"
        +]
      • addedOutput schema / $defs / MessageSample / properties / summary / type
        Added value: +[
        +  "object",
        +  "null"
        +]
      • removedOutput schema / $defs / OdometrySummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angular_z": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`twist.twist.angular.z`, rad/s."
        -    },
        -    "child_frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`child_frame_id` (frame of the twist)."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` (frame of the pose)."
        -    },
        -    "linear_speed": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `twist.twist.linear`, m/s."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when a value is missing; else `null`."
        -    },
        -    "position_x": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.x`, m."
        -    },
        -    "position_y": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.y`, m."
        -    },
        -    "position_z": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.z`, m."
        -    },
        -    "summary_type": {
        -      "const": "odometry",
        -      "type": "string"
        -    },
        -    "yaw": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Heading from the pose quaternion, radians."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "child_frame_id",
        -    "linear_speed",
        -    "angular_z",
        -    "position_x",
        -    "position_y",
        -    "position_z",
        -    "yaw"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / PointCloud2Summary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "fields": {
        -      "description": "Point layout.",
        -      "items": {
        -        "$ref": "#/$defs/PointFieldSummary"
        -      },
        -      "type": "array"
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the cloud."
        -    },
        -    "height": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rows (1: unorganized)."
        -    },
        -    "is_dense": {
        -      "anyOf": [
        -        {
        -          "type": "boolean"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "True when no point is invalid."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when partial; else `null`."
        -    },
        -    "point_count": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`width` times `height`."
        -    },
        -    "point_step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bytes per point."
        -    },
        -    "row_step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bytes per row."
        -    },
        -    "summary_type": {
        -      "const": "point_cloud2",
        -      "type": "string"
        -    },
        -    "width": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Points per row."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "width",
        -    "height",
        -    "point_count",
        -    "point_step",
        -    "row_step",
        -    "is_dense",
        -    "fields"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / PointFieldSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "count": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Elements per point."
        -    },
        -    "datatype": {
        -      "description": "Element type, e.g. `FLOAT32`.",
        -      "type": "string"
        -    },
        -    "name": {
        -      "description": "Field name, e.g. `x`.",
        -      "type": "string"
        -    },
        -    "offset": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Byte offset in a point."
        -    }
        -  },
        -  "required": [
        -    "name",
        -    "datatype",
        -    "offset",
        -    "count"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanReturn
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "beam_index": {
        -      "description": "Index in `ranges`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "bearing": {
        -      "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.",
        -      "type": "number"
        -    },
        -    "range": {
        -      "description": "Distance, meters.",
        -      "type": "number"
        -    }
        -  },
        -  "required": [
        -    "range",
        -    "bearing",
        -    "beam_index"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanSector
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "beam_count": {
        -      "description": "Beams inside the sector (0: not covered).",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "closest": {
        -      "anyOf": [
        -        {
        -          "$ref": "#/$defs/ScanReturn"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Closest valid return in the sector; `null` with `note`."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Why `closest` is `null`; `null` when it is set."
        -    }
        -  },
        -  "required": [
        -    "beam_count",
        -    "closest"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanSectors
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "front": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings within +-45 degrees of +x."
        -    },
        -    "left": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings from 45 to 135 degrees."
        -    },
        -    "rear": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings beyond +-135 degrees."
        -    },
        -    "right": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings from -135 to -45 degrees."
        -    }
        -  },
        -  "required": [
        -    "front",
        -    "left",
        -    "right",
        -    "rear"
        -  ],
        -  "type": "object"
        -}
      • addedOutput schema / $defs / TopicRate / properties / clock_basis
        Added value: +{
        +  "description": "Stamp clock: `wall` (stamps are dates), `sim` (since a start), `none` (no stamps), `mixed` (several kinds, e.g. two publishers).",
        +  "enum": [
        +    "wall",
        +    "sim",
        +    "none",
        +    "mixed"
        +  ],
        +  "type": "string"
        +}
      • removedOutput schema / $defs / TopicRate / properties / interval_cv / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / interval_cv / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / interval_median_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / interval_median_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / max_gap_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / max_gap_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / mean_interval_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / mean_interval_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / observed_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / observed_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / sim_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / sim_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • addedOutput schema / $defs / TopicRate / properties / stamp_age_median_s
        Added value: +{
        +  "description": "Median of arrival minus stamp, s, if `wall`; negative = publisher ahead.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / $defs / TopicRate / properties / stamp_age_p95_s
        Added value: +{
        +  "description": "95th percentile of that age (max under 20 messages).",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / $defs / TopicRate / properties / stamp_regression_count
        Added value: +{
        +  "description": "Times a stamp went back versus the previous one.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • removedOutput schema / $defs / TopicRate / properties / trailing_gap_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / trailing_gap_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rate / default
        Removed value: -null
    • Changedsample_messages44 fields changed
      • addedInput schema / properties / min_log_level
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "debug",
        +        "info",
        +        "warn",
        +        "error",
        +        "fatal"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "`/rosout` (`rcl_interfaces/msg/Log`) only: keep messages at or above this level (debug 10, info 20, warn 30, error 40, fatal 50). Other topics: error."
        +}
      • addedInput schema / properties / node
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "`/rosout` only: keep logs of this node and its child loggers, e.g. `/robot1/driver` (also `driver`, `robot1.driver`). Exact name, never a prefix. Other topics: error."
        +}
      • removedOutput schema / $defs / ImageSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "data_length": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixel buffer size in bytes (see `data_length_basis`)."
        -    },
        -    "data_length_basis": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "payload",
        -            "step_x_height"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)."
        -    },
        -    "encoding": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixel encoding, e.g. `rgb8`."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the camera."
        -    },
        -    "height": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixels."
        -    },
        -    "is_bigendian": {
        -      "anyOf": [
        -        {
        -          "type": "boolean"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Big-endian pixels."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when partial; else `null`."
        -    },
        -    "step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Row length, bytes."
        -    },
        -    "summary_type": {
        -      "const": "image",
        -      "type": "string"
        -    },
        -    "width": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Pixels."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "width",
        -    "height",
        -    "encoding",
        -    "step",
        -    "is_bigendian",
        -    "data_length",
        -    "data_length_basis"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ImuSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angular_velocity_norm": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `angular_velocity`, rad/s."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the IMU."
        -    },
        -    "linear_acceleration_norm": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `linear_acceleration`, m/s^2."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when a value is unusable; else `null`."
        -    },
        -    "orientation_valid": {
        -      "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.",
        -      "type": "boolean"
        -    },
        -    "pitch": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about y, radians."
        -    },
        -    "roll": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about x, radians."
        -    },
        -    "summary_type": {
        -      "const": "imu",
        -      "type": "string"
        -    },
        -    "yaw": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rotation about z, radians."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "orientation_valid",
        -    "roll",
        -    "pitch",
        -    "yaw",
        -    "angular_velocity_norm",
        -    "linear_acceleration_norm"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / LaserScanSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angle_increment": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Angle between beams, rad."
        -    },
        -    "angle_max": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bearing of the last beam, rad."
        -    },
        -    "angle_min": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bearing of beam 0, rad."
        -    },
        -    "beam_count": {
        -      "description": "Beams in `ranges`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "closest_obstacle": {
        -      "anyOf": [
        -        {
        -          "$ref": "#/$defs/ScanReturn"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Closest valid return of the scan; `null` with `note`."
        -    },
        -    "finite_count": {
        -      "description": "Beams with a finite range.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame."
        -    },
        -    "inf_count": {
        -      "description": "Beams at `+inf`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "nan_count": {
        -      "description": "Beams at `nan`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "neg_inf_count": {
        -      "description": "Beams at `-inf`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when the summary is partial; else `null`."
        -    },
        -    "out_of_range_count": {
        -      "description": "Finite ranges outside `range_min`..`range_max` (not valid).",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "range_max": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Largest valid range, m."
        -    },
        -    "range_min": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Smallest valid range, m."
        -    },
        -    "sectors": {
        -      "$ref": "#/$defs/ScanSectors",
        -      "description": "Closest valid return per sector."
        -    },
        -    "summary_type": {
        -      "const": "laser_scan",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "beam_count",
        -    "angle_min",
        -    "angle_max",
        -    "angle_increment",
        -    "range_min",
        -    "range_max",
        -    "finite_count",
        -    "inf_count",
        -    "neg_inf_count",
        -    "nan_count",
        -    "out_of_range_count",
        -    "closest_obstacle",
        -    "sectors"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / MessageSample / properties / received_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / received_ns / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / received_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / anyOf
        Removed value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / recorded_ns / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / $defs / MessageSample / properties / summary / anyOf
        Removed value: -[
        -  {
        -    "discriminator": {
        -      "mapping": {
        -        "image": "#/$defs/ImageSummary",
        -        "imu": "#/$defs/ImuSummary",
        -        "laser_scan": "#/$defs/LaserScanSummary",
        -        "odometry": "#/$defs/OdometrySummary",
        -        "point_cloud2": "#/$defs/PointCloud2Summary"
        -      },
        -      "propertyName": "summary_type"
        -    },
        -    "oneOf": [
        -      {
        -        "$ref": "#/$defs/LaserScanSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/OdometrySummary"
        -      },
        -      {
        -        "$ref": "#/$defs/ImuSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/ImageSummary"
        -      },
        -      {
        -        "$ref": "#/$defs/PointCloud2Summary"
        -      }
        -    ]
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / summary / default
        Removed value: -null
      • addedOutput schema / $defs / MessageSample / properties / summary / properties
        Added value: +{
        +  "summary_type": {
        +    "enum": [
        +      "laser_scan",
        +      "odometry",
        +      "imu",
        +      "image",
        +      "point_cloud2"
        +    ],
        +    "type": "string"
        +  }
        +}
      • addedOutput schema / $defs / MessageSample / properties / summary / required
        Added value: +[
        +  "summary_type"
        +]
      • addedOutput schema / $defs / MessageSample / properties / summary / type
        Added value: +[
        +  "object",
        +  "null"
        +]
      • removedOutput schema / $defs / OdometrySummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "angular_z": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`twist.twist.angular.z`, rad/s."
        -    },
        -    "child_frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`child_frame_id` (frame of the twist)."
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` (frame of the pose)."
        -    },
        -    "linear_speed": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Norm of `twist.twist.linear`, m/s."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when a value is missing; else `null`."
        -    },
        -    "position_x": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.x`, m."
        -    },
        -    "position_y": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.y`, m."
        -    },
        -    "position_z": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`pose.pose.position.z`, m."
        -    },
        -    "summary_type": {
        -      "const": "odometry",
        -      "type": "string"
        -    },
        -    "yaw": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Heading from the pose quaternion, radians."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "child_frame_id",
        -    "linear_speed",
        -    "angular_z",
        -    "position_x",
        -    "position_y",
        -    "position_z",
        -    "yaw"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / PointCloud2Summary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "fields": {
        -      "description": "Point layout.",
        -      "items": {
        -        "$ref": "#/$defs/PointFieldSummary"
        -      },
        -      "type": "array"
        -    },
        -    "frame_id": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`header.frame_id` of the cloud."
        -    },
        -    "height": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Rows (1: unorganized)."
        -    },
        -    "is_dense": {
        -      "anyOf": [
        -        {
        -          "type": "boolean"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "True when no point is invalid."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "One sentence when partial; else `null`."
        -    },
        -    "point_count": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "`width` times `height`."
        -    },
        -    "point_step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bytes per point."
        -    },
        -    "row_step": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Bytes per row."
        -    },
        -    "summary_type": {
        -      "const": "point_cloud2",
        -      "type": "string"
        -    },
        -    "width": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Points per row."
        -    }
        -  },
        -  "required": [
        -    "summary_type",
        -    "frame_id",
        -    "width",
        -    "height",
        -    "point_count",
        -    "point_step",
        -    "row_step",
        -    "is_dense",
        -    "fields"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / PointFieldSummary
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "count": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Elements per point."
        -    },
        -    "datatype": {
        -      "description": "Element type, e.g. `FLOAT32`.",
        -      "type": "string"
        -    },
        -    "name": {
        -      "description": "Field name, e.g. `x`.",
        -      "type": "string"
        -    },
        -    "offset": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Byte offset in a point."
        -    }
        -  },
        -  "required": [
        -    "name",
        -    "datatype",
        -    "offset",
        -    "count"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanReturn
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "beam_index": {
        -      "description": "Index in `ranges`.",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "bearing": {
        -      "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.",
        -      "type": "number"
        -    },
        -    "range": {
        -      "description": "Distance, meters.",
        -      "type": "number"
        -    }
        -  },
        -  "required": [
        -    "range",
        -    "bearing",
        -    "beam_index"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanSector
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "beam_count": {
        -      "description": "Beams inside the sector (0: not covered).",
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    "closest": {
        -      "anyOf": [
        -        {
        -          "$ref": "#/$defs/ScanReturn"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "description": "Closest valid return in the sector; `null` with `note`."
        -    },
        -    "note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Why `closest` is `null`; `null` when it is set."
        -    }
        -  },
        -  "required": [
        -    "beam_count",
        -    "closest"
        -  ],
        -  "type": "object"
        -}
      • removedOutput schema / $defs / ScanSectors
        Removed value: -{
        -  "additionalProperties": false,
        -  "properties": {
        -    "front": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings within +-45 degrees of +x."
        -    },
        -    "left": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings from 45 to 135 degrees."
        -    },
        -    "rear": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings beyond +-135 degrees."
        -    },
        -    "right": {
        -      "$ref": "#/$defs/ScanSector",
        -      "description": "Bearings from -135 to -45 degrees."
        -    }
        -  },
        -  "required": [
        -    "front",
        -    "left",
        -    "right",
        -    "rear"
        -  ],
        -  "type": "object"
        -}
      • addedOutput schema / $defs / TopicRate / properties / clock_basis
        Added value: +{
        +  "description": "Stamp clock: `wall` (stamps are dates), `sim` (since a start), `none` (no stamps), `mixed` (several kinds, e.g. two publishers).",
        +  "enum": [
        +    "wall",
        +    "sim",
        +    "none",
        +    "mixed"
        +  ],
        +  "type": "string"
        +}
      • removedOutput schema / $defs / TopicRate / properties / interval_cv / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / interval_cv / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / interval_median_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / interval_median_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / max_gap_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / max_gap_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / mean_interval_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / mean_interval_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / observed_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / observed_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / $defs / TopicRate / properties / sim_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / sim_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • addedOutput schema / $defs / TopicRate / properties / stamp_age_median_s
        Added value: +{
        +  "description": "Median of arrival minus stamp, s, if `wall`; negative = publisher ahead.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / $defs / TopicRate / properties / stamp_age_p95_s
        Added value: +{
        +  "description": "95th percentile of that age (max under 20 messages).",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / $defs / TopicRate / properties / stamp_regression_count
        Added value: +{
        +  "description": "Times a stamp went back versus the previous one.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • removedOutput schema / $defs / TopicRate / properties / trailing_gap_s / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / $defs / TopicRate / properties / trailing_gap_s / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rate / default
        Removed value: -null
    • Changedtopic_metrics25 fields changed
      • removedOutput schema / properties / declared_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / declared_frequency_hz / default
        Removed value: -null
      • addedOutput schema / properties / declared_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / latency_available / default
        Removed value: -false
      • removedOutput schema / properties / latency_ns_p50 / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / latency_ns_p50 / default
        Removed value: -null
      • addedOutput schema / properties / latency_ns_p50 / minimum
        Added value: +0
      • addedOutput schema / properties / latency_ns_p50 / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / latency_ns_p95 / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / latency_ns_p95 / default
        Removed value: -null
      • addedOutput schema / properties / latency_ns_p95 / minimum
        Added value: +0
      • addedOutput schema / properties / latency_ns_p95 / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / latency_ns_p99 / anyOf
        Removed value: -[
        -  {
        -    "minimum": 0,
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / latency_ns_p99 / default
        Removed value: -null
      • addedOutput schema / properties / latency_ns_p99 / minimum
        Added value: +0
      • addedOutput schema / properties / latency_ns_p99 / type
        Added value: +[
        +  "integer",
        +  "null"
        +]
      • removedOutput schema / properties / note / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / note / default
        Removed value: -null
      • addedOutput schema / properties / note / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / observed_frequency_hz / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / properties / observed_frequency_hz / default
        Removed value: -null
      • addedOutput schema / properties / observed_frequency_hz / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / sequence_gaps_count / default
        Removed value: -0
      • removedOutput schema / properties / sequence_numbers_available / default
        Removed value: -false
      • removedOutput schema / properties / status / default
        Removed value: -"ok"
  2. 14 tool updatesv0.7.1
    • Changedanalyze_bag42 fields changed
      • changedInput schema / properties / path / description
        Previous value: -"Path to a bag: a file ending in `.mcap` or `.db3`, or a `rosbag2_*` directory. `peek_bag_samples` also reads ROS 1 `.bag` files; `analyze_bag` does not (`ros2 bag info` cannot open them). Leading/trailing whitespace is stripped. Null bytes and otherwise malformed filesystem paths are rejected. Existence and bag format are validated by the live adapter (mock mode accepts any well-formed path)."New value: +"Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path."
      • removedInput schema / properties / path / title
        Removed value: -"Path"
      • removedInput schema / title
        Removed value: -"analyze_bagArguments"
      • removedOutput schema / $defs / BagTopicStats / description
        Removed value: -"Per-topic statistics inside a bag analysis result."
      • changedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / description
        Previous value: -"Recorded time of the first message on this topic, in nanoseconds (bag receive time, normally epoch). `null` when the bag was summarized from `ros2 bag info` text only, or the topic has no messages."New value: +"Recorded ns of the first message; `null` for text-only summaries or no messages."
      • removedOutput schema / $defs / BagTopicStats / properties / first_timestamp_ns / title
        Removed value: -"First Timestamp Ns"
      • changedOutput schema / $defs / BagTopicStats / properties / frequency_basis / description
        Previous value: -"How `frequency_hz` was computed: `topic_span` (per-topic first and last message times, the accurate one) or `bag_duration` (count divided by whole-bag duration, used when per-topic times could not be read). `null` when there is no rate."New value: +"`topic_span` (accurate) or `bag_duration` (count over the whole bag, understates late topics); `null` without a rate."
      • removedOutput schema / $defs / BagTopicStats / properties / frequency_basis / title
        Removed value: -"Frequency Basis"
      • changedOutput schema / $defs / BagTopicStats / properties / frequency_hz / description
        Previous value: -"Average publish rate in Hz. With `frequency_basis` `topic_span` it is `(message_count - 1) / (last - first message time)` for this topic; with `bag_duration` it is `message_count / bag duration`, which understates the rate of a topic that started late or stopped early. `null` when fewer than 2 messages, a zero time span, or the topic is `latched` and all its messages fall within 1 second (a start-up burst, not a rate). A latched topic published over a longer span keeps its rate. Not a periodic rate for event-driven topics such as `/parameter_events`."New value: +"Average Hz: (count - 1) over the topic's own time span (see `frequency_basis`); `null` under 2 messages, a zero span, or a latched start-up burst (all within 1 s)."
      • removedOutput schema / $defs / BagTopicStats / properties / frequency_hz / title
        Removed value: -"Frequency Hz"
      • addedOutput schema / $defs / BagTopicStats / properties / kind
        Added value: +{
        +  "default": "user",
        +  "description": "`user` (data, incl. `/tf` and `/clock`), `rosbag2_internal` or `ros_builtin` (e.g. `/rosout`).",
        +  "enum": [
        +    "user",
        +    "rosbag2_internal",
        +    "ros_builtin"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / description
        Previous value: -"Recorded time of the last message on this topic; same rules as `first_timestamp_ns`."New value: +"Recorded ns of the last message; same rules."
      • removedOutput schema / $defs / BagTopicStats / properties / last_timestamp_ns / title
        Removed value: -"Last Timestamp Ns"
      • changedOutput schema / $defs / BagTopicStats / properties / latched / description
        Previous value: -"True when a publisher recorded this topic with transient_local durability (late joiners get the last samples, e.g. `/tf_static`, `/rosout`). Such a topic often publishes a start-up burst; when all its messages fall within 1 second `frequency_hz` is `null`. `false` when every recorded publisher was volatile; `null` when the bag carries no QoS information (ROS 1 bags report their latching flag)."New value: +"True if recorded with transient_local durability, false if volatile, `null` without QoS information."
      • removedOutput schema / $defs / BagTopicStats / properties / latched / title
        Removed value: -"Latched"
      • removedOutput schema / $defs / BagTopicStats / properties / message_count / title
        Removed value: -"Message Count"
      • removedOutput schema / $defs / BagTopicStats / properties / message_type / description
        Removed value: -"ROS2 message type recorded for this topic."
      • removedOutput schema / $defs / BagTopicStats / properties / message_type / title
        Removed value: -"Message Type"
      • removedOutput schema / $defs / BagTopicStats / properties / name / description
        Removed value: -"Fully qualified topic name as recorded in the bag."
      • removedOutput schema / $defs / BagTopicStats / properties / name / title
        Removed value: -"Name"
      • removedOutput schema / $defs / BagTopicStats / title
        Removed value: -"BagTopicStats"
      • removedOutput schema / $defs / ParticipantInfo
        Removed value: -{
        -  "additionalProperties": false,
        -  "description": "DDS participant discovered on the configured domain.\n\nCarries identity (`guid`, `name`, `vendor`) plus lifecycle fields\n(`first_seen_ns`, `last_seen_ns`, `status`, `seen_count`).",
        -  "properties": {
        -    "announced_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Nanoseconds since epoch (DDS source timestamp of the participant's most recent announcement, taken from the announcing side's clock; the local clock when the announcer sent none). `None` when the backend does not expose it. Distinct from `first_seen_ns` / `last_seen_ns`, which are TopicForge's own local clock.",
        -      "title": "Announced Ns"
        -    },
        -    "domain_id": {
        -      "description": "DDS domain id the participant is bound to.",
        -      "maximum": 232,
        -      "minimum": 0,
        -      "title": "Domain Id",
        -      "type": "integer"
        -    },
        -    "first_seen_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Wall-clock timestamp (nanoseconds since epoch) of the **first** discovery sample TopicForge observed for this participant. `None` when the adapter does not track lifecycle.",
        -      "title": "First Seen Ns"
        -    },
        -    "guid": {
        -      "description": "DDS Global Unique Identifier of the participant: hex string, stable across discovery events within a single deployment.",
        -      "title": "Guid",
        -      "type": "string"
        -    },
        -    "hostname": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Hostname announced in discovery (Cyclone `__Hostname` property). `None` when the vendor does not announce it.",
        -      "title": "Hostname"
        -    },
        -    "is_observer": {
        -      "default": false,
        -      "description": "True for TopicForge's own read-only observer participant, which joins the domain to watch it and appears in this list; false for every other participant.",
        -      "title": "Is Observer",
        -      "type": "boolean"
        -    },
        -    "last_seen_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Wall-clock timestamp (nanoseconds since epoch) of the **most recent** discovery sample observed. Used by `participant_events` to distinguish currently-active participants from those that left the bus. `None` when the adapter does not track lifecycle.",
        -      "title": "Last Seen Ns"
        -    },
        -    "lost_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Nanoseconds since epoch of the moment the participant left, once `status` is `left`; `None` while it is active. How precise it is depends on `lost_time_source`. It is an upper bound of when the participant died: after a clean shutdown it is the exact leave time, after a crash it is when the lease expired, and the two cannot be told apart. The process died at or before `lost_ns`, and at most one lease earlier (10 s Cyclone default, 20 s Fast, 100 s RTI).",
        -      "title": "Lost Ns"
        -    },
        -    "lost_time_source": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "dds_source_timestamp",
        -            "observed_local"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Where `lost_ns` comes from: `dds_source_timestamp` (timestamp carried by the discovery dispose) or `observed_local` (the moment TopicForge noticed, the weakest). `None` while active.",
        -      "title": "Lost Time Source"
        -    },
        -    "mode_effective": {
        -      "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.",
        -      "enum": [
        -        "mock",
        -        "live"
        -      ],
        -      "title": "Mode Effective",
        -      "type": "string"
        -    },
        -    "name": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Participant name the application announced through the standard DDS EntityName QoS (PID_ENTITY_NAME in RTPS discovery), for example `lidar_driver`. `None` when the application did not set one (Dust DDS cannot) or the backend does not expose it.",
        -      "title": "Name"
        -    },
        -    "seen_count": {
        -      "default": 1,
        -      "description": "Number of distinct discovery samples observed for this participant across all calls to `list_participants` during this server's lifetime. Starts at 1 and increments on each observation.",
        -      "minimum": 1,
        -      "title": "Seen Count",
        -      "type": "integer"
        -    },
        -    "status": {
        -      "default": "unknown",
        -      "description": "Lifecycle status. `active` means TopicForge has a recent discovery sample for this GUID and the bus has not signalled removal. `left` means the participant was observed earlier but has since disappeared (a Fast DDS `REMOVED` callback or a Cyclone polling delta). `unknown` when the adapter does not track lifecycle.",
        -      "enum": [
        -        "active",
        -        "left",
        -        "unknown"
        -      ],
        -      "title": "Status",
        -      "type": "string"
        -    },
        -    "vendor": {
        -      "description": "DDS implementation that announced this participant, decoded from the OMG-RTPS `vendor_id` field on the discovery sample. `cyclone` (Eclipse Cyclone DDS), `fast` (eProsima Fast DDS), `rti` (RTI Connext), `rti_micro` (RTI Connext Micro), `opensplice` (ADLink OpenSplice), `opendds` (OCI OpenDDS), `coredx` (Twin Oaks CoreDX), `intercom` (Kongsberg InterCOM), `dust` (S2E Dust DDS). `mock` is reserved for synthetic fixtures. `unknown` means the vendor could not be determined: TopicForge reads it from the vendor prefix of the participant GUID, and some vendors (for example Dust DDS and RTI Connext) do not put their vendor id there, while the Cyclone Python binding does not expose the RTPS header vendor id. The participant is still reported; see `vendor_source`. Any conformant DDS-RTPS participant should appear through standard discovery; Cyclone DDS and Dust DDS have been observed on a live bus. See `docs/dds-interop-matrix.md`.",
        -      "enum": [
        -        "cyclone",
        -        "fast",
        -        "rti",
        -        "rti_micro",
        -        "opensplice",
        -        "opendds",
        -        "coredx",
        -        "intercom",
        -        "dust",
        -        "mock",
        -        "unknown"
        -      ],
        -      "title": "Vendor",
        -      "type": "string"
        -    },
        -    "vendor_source": {
        -      "default": "none",
        -      "description": "Where `vendor` came from: `guid_prefix` when it was read from the vendor prefix of the participant GUID, `none` when the vendor is unknown (or synthetic in mock mode).",
        -      "enum": [
        -        "guid_prefix",
        -        "none"
        -      ],
        -      "title": "Vendor Source",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "guid",
        -    "vendor",
        -    "domain_id",
        -    "mode_effective"
        -  ],
        -  "title": "ParticipantInfo",
        -  "type": "object"
        -}
      • changedOutput schema / description
        Previous value: -"Structured summary of a ROS2 bag.\n\nThe fields `bag_format`, `samples_decoded_count`,\n`recording_duration_ns` and `participants_recorded` are populated only\nwhen the `rosbags`-backed reader runs; the `ros2 bag info` path leaves\nthem at their defaults."New value: +"Structured summary of a ROS2 bag."
      • changedOutput schema / properties / anomalies / description
        Previous value: -"Human-readable notes about gaps, clock jumps, or other oddities. Populated in mock mode only; live mode does not detect anomalies."New value: +"Notes on gaps or clock jumps; mock mode only."
      • removedOutput schema / properties / anomalies / title
        Removed value: -"Anomalies"
      • changedOutput schema / properties / bag_format / description
        Previous value: -"Concrete bag container format detected by the reader: `mcap` (Foxglove MCAP), `db3` (ROS2 rosbag2 SQLite), `bag` (ROS1 legacy chunked), or `unknown` when the reader could not classify. `None` when the bag was summarized from `ros2 bag info` text, which carries no format information."New value: +"Detected container: `mcap`, `db3`, `bag` or `unknown`; `None` for `ros2 bag info` text."
      • removedOutput schema / properties / bag_format / title
        Removed value: -"Bag Format"
      • addedOutput schema / properties / duration_s
        Added value: +{
        +  "description": "Bag duration in seconds.",
        +  "minimum": 0,
        +  "type": "number"
        +}
      • removedOutput schema / properties / duration_seconds
        Removed value: -{
        -  "description": "Total bag duration, in seconds (wall clock between first and last message).",
        -  "minimum": 0,
        -  "title": "Duration Seconds",
        -  "type": "number"
        -}
      • removedOutput schema / properties / message_count / title
        Removed value: -"Message Count"
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • changedOutput schema / properties / note / description
        Previous value: -"Why the result is less detailed than usual, for example per-topic rates computed over the whole bag duration because the bag was too large to read per-topic message times. `None` when there is nothing to add."New value: +"Why the result is less detailed than usual (e.g. rates over the whole bag duration); else `None`."
      • removedOutput schema / properties / note / title
        Removed value: -"Note"
      • removedOutput schema / properties / participants_recorded
        Removed value: -{
        -  "description": "DDS participants recorded in the bag when the container format embeds participant metadata. MCAP can carry it via channel metadata records; ROS2 `.db3` and ROS1 `.bag` generally do not. Empty list when not available, which is the common case.",
        -  "items": {
        -    "$ref": "#/$defs/ParticipantInfo"
        -  },
        -  "title": "Participants Recorded",
        -  "type": "array"
        -}
      • changedOutput schema / properties / path / description
        Previous value: -"Path to the analyzed bag, as supplied by the caller. May point to a file (`.mcap`, `.db3`, or ROS 1 `.bag` for `peek_bag_samples`) or to a `rosbag2_*` directory."New value: +"Bag path as supplied."
      • removedOutput schema / properties / path / title
        Removed value: -"Path"
      • removedOutput schema / properties / recording_duration_ns
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Recording duration in nanoseconds when readable from the bag's index. `None` when only `ros2 bag info` text was parsed; `duration_seconds` (float) is the always-populated fallback that downstream LLM consumers should prefer when this is `None`.",
        -  "title": "Recording Duration Ns"
        -}
      • removedOutput schema / properties / samples_decoded_count
        Removed value: -{
        -  "default": 0,
        -  "description": "Total decoded sample count across all topics produced by the bag reader. `0` when the reader only parsed metadata or when `rosbags` is not installed on the host. Use `peek_bag_samples` to pull the actual sample payloads for a specific topic.",
        -  "minimum": 0,
        -  "title": "Samples Decoded Count",
        -  "type": "integer"
        -}
      • removedOutput schema / properties / storage_format / title
        Removed value: -"Storage Format"
      • removedOutput schema / properties / topics / title
        Removed value: -"Topics"
      • changedOutput schema / required
        Previous value: -[
        -  "path",
        -  "duration_seconds",
        -  "message_count",
        -  "topics",
        -  "mode_effective"
        -]New value: +[
        +  "path",
        +  "duration_s",
        +  "message_count",
        +  "topics",
        +  "mode_effective"
        +]
    • Changeddetect_qos_mismatches110 fields changed
      • changedInput schema / properties / topic / description
        Previous value: -"Optional DDS topic name to scope the scan to: a bare name such as `scan` or a ROS 2 mangled name such as `rt/scan`. Omit to scan all topics."New value: +"Optional DDS topic to scope the scan to: `scan` or `rt/scan`. Omit to scan all topics."
      • removedInput schema / properties / topic / title
        Removed value: -"Topic"
      • removedInput schema / title
        Removed value: -"detect_qos_mismatchesArguments"
      • removedOutput schema / $defs / MatchedPair / description
        Removed value: -"A reader and a writer that DDS will connect given their announced QoS."
      • changedOutput schema / $defs / MatchedPair / properties / late_joiner / description
        Previous value: -"True when the writer is VOLATILE and the reader was announced more than 1 s after it, both on the same host: samples published before the reader joined are not delivered to it. Normal for a VOLATILE writer, not a fault."New value: +"True for a VOLATILE writer whose reader joined over 1 s later on the same host: earlier samples are not delivered. Normal."
      • removedOutput schema / $defs / MatchedPair / properties / late_joiner / title
        Removed value: -"Late Joiner"
      • removedOutput schema / $defs / MatchedPair / properties / late_joiner_note / title
        Removed value: -"Late Joiner Note"
      • removedOutput schema / $defs / MatchedPair / properties / reader_guid / description
        Removed value: -"GUID of the reader endpoint."
      • removedOutput schema / $defs / MatchedPair / properties / reader_guid / title
        Removed value: -"Reader Guid"
      • removedOutput schema / $defs / MatchedPair / properties / reader_participant_guid / description
        Removed value: -"GUID of the reader's participant."
      • removedOutput schema / $defs / MatchedPair / properties / reader_participant_guid / title
        Removed value: -"Reader Participant Guid"
      • removedOutput schema / $defs / MatchedPair / properties / reader_participant_name / description
        Removed value: -"Announced name of the reader's participant."
      • removedOutput schema / $defs / MatchedPair / properties / reader_participant_name / title
        Removed value: -"Reader Participant Name"
      • removedOutput schema / $defs / MatchedPair / properties / topic / description
        Removed value: -"Topic name both endpoints use."
      • removedOutput schema / $defs / MatchedPair / properties / topic / title
        Removed value: -"Topic"
      • removedOutput schema / $defs / MatchedPair / properties / type_name / description
        Removed value: -"Type name both announced."
      • removedOutput schema / $defs / MatchedPair / properties / type_name / title
        Removed value: -"Type Name"
      • removedOutput schema / $defs / MatchedPair / properties / writer_guid / description
        Removed value: -"GUID of the writer endpoint."
      • removedOutput schema / $defs / MatchedPair / properties / writer_guid / title
        Removed value: -"Writer Guid"
      • removedOutput schema / $defs / MatchedPair / properties / writer_participant_guid / description
        Removed value: -"GUID of the writer's participant."
      • removedOutput schema / $defs / MatchedPair / properties / writer_participant_guid / title
        Removed value: -"Writer Participant Guid"
      • removedOutput schema / $defs / MatchedPair / properties / writer_participant_name / description
        Removed value: -"Announced name of the writer's participant."
      • removedOutput schema / $defs / MatchedPair / properties / writer_participant_name / title
        Removed value: -"Writer Participant Name"
      • removedOutput schema / $defs / MatchedPair / title
        Removed value: -"MatchedPair"
      • removedOutput schema / $defs / MismatchReport / description
        Removed value: -"A single reader/writer QoS incompatibility detected on a topic.\n\nOnly pairs that share a partition and a type name are reported here: those\nseparated by partition or type are `NotMatchedPair`s in the same scan."
      • changedOutput schema / $defs / MismatchReport / properties / details / description
        Previous value: -"Requested and offered value and the failed rule, one entry per policy."New value: +"Requested and offered value and the failed rule, per policy."
      • removedOutput schema / $defs / MismatchReport / properties / details / title
        Removed value: -"Details"
      • changedOutput schema / $defs / MismatchReport / properties / incompatible_policies / description
        Previous value: -"Names of the QoS policies that block communication or risk degradation: see `details` for the compared values."New value: +"Blocking or risky policies; values in `details`."
      • removedOutput schema / $defs / MismatchReport / properties / incompatible_policies / title
        Removed value: -"Incompatible Policies"
      • removedOutput schema / $defs / MismatchReport / properties / mode_effective
        Removed value: -{
        -  "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.",
        -  "enum": [
        -    "mock",
        -    "live"
        -  ],
        -  "title": "Mode Effective",
        -  "type": "string"
        -}
      • removedOutput schema / $defs / MismatchReport / properties / reader_guid / description
        Removed value: -"GUID of the reader endpoint involved in the mismatch, if known."
      • removedOutput schema / $defs / MismatchReport / properties / reader_guid / title
        Removed value: -"Reader Guid"
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_guid / description
        Removed value: -"GUID of the participant that owns the reader, if known."
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_guid / title
        Removed value: -"Reader Participant Guid"
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_name / description
        Removed value: -"Announced name of the reader's participant, if it set one."
      • removedOutput schema / $defs / MismatchReport / properties / reader_participant_name / title
        Removed value: -"Reader Participant Name"
      • removedOutput schema / $defs / MismatchReport / properties / reader_type_name / description
        Removed value: -"Type name the reader announced."
      • removedOutput schema / $defs / MismatchReport / properties / reader_type_name / title
        Removed value: -"Reader Type Name"
      • changedOutput schema / $defs / MismatchReport / properties / severity / description
        Previous value: -"`incompatible` means communication is definitely blocked; `risky` means it may degrade but is not strictly blocked by the DDS spec (History only). Useful for an LLM to triage user-facing advice."New value: +"`incompatible` (blocked) or `risky` (may degrade; History only)."
      • removedOutput schema / $defs / MismatchReport / properties / severity / title
        Removed value: -"Severity"
      • addedOutput schema / $defs / MismatchReport / properties / suggested_fixes
        Added value: +{
        +  "description": "Fixes as text for a human to apply, never applied; empty when none.",
        +  "items": {
        +    "$ref": "#/$defs/SuggestedFix"
        +  },
        +  "type": "array"
        +}
      • removedOutput schema / $defs / MismatchReport / properties / topic / description
        Removed value: -"Topic name where the mismatch was detected."
      • removedOutput schema / $defs / MismatchReport / properties / topic / title
        Removed value: -"Topic"
      • changedOutput schema / $defs / MismatchReport / properties / unchecked / description
        Previous value: -"Policies that could not be compared for this pair because one side did not announce a value."New value: +"Policies not compared because one side announced no value."
      • removedOutput schema / $defs / MismatchReport / properties / unchecked / title
        Removed value: -"Unchecked"
      • removedOutput schema / $defs / MismatchReport / properties / writer_guid / description
        Removed value: -"GUID of the writer endpoint involved in the mismatch, if known."
      • removedOutput schema / $defs / MismatchReport / properties / writer_guid / title
        Removed value: -"Writer Guid"
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_guid / description
        Removed value: -"GUID of the participant that owns the writer, if known."
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_guid / title
        Removed value: -"Writer Participant Guid"
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_name / description
        Removed value: -"Announced name of the writer's participant, if it set one."
      • removedOutput schema / $defs / MismatchReport / properties / writer_participant_name / title
        Removed value: -"Writer Participant Name"
      • removedOutput schema / $defs / MismatchReport / properties / writer_type_name / description
        Removed value: -"Type name the writer announced."
      • removedOutput schema / $defs / MismatchReport / properties / writer_type_name / title
        Removed value: -"Writer Type Name"
      • changedOutput schema / $defs / MismatchReport / required
        Previous value: -[
        -  "topic",
        -  "incompatible_policies",
        -  "severity",
        -  "mode_effective"
        -]New value: +[
        +  "topic",
        +  "incompatible_policies",
        +  "severity"
        +]
      • removedOutput schema / $defs / MismatchReport / title
        Removed value: -"MismatchReport"
      • removedOutput schema / $defs / NotMatchedPair / description
        Removed value: -"A reader and a writer on one topic that DDS will not match, whatever their QoS."
      • removedOutput schema / $defs / NotMatchedPair / properties / detail / title
        Removed value: -"Detail"
      • changedOutput schema / $defs / NotMatchedPair / properties / latent_incompatible_policies / description
        Previous value: -"RxO policies that would ALSO be incompatible once the partition / type issue is fixed (same shape as `MismatchReport.details`). Empty when the QoS of the two endpoints would be compatible, or could not be compared."New value: +"RxO policies that would ALSO be incompatible once the partition or type is fixed (shape of `MismatchReport.details`)."
      • removedOutput schema / $defs / NotMatchedPair / properties / latent_incompatible_policies / title
        Removed value: -"Latent Incompatible Policies"
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_guid / description
        Removed value: -"GUID of the reader endpoint."
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_guid / title
        Removed value: -"Reader Guid"
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_participant_guid / description
        Removed value: -"GUID of the reader's participant."
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_participant_guid / title
        Removed value: -"Reader Participant Guid"
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_participant_name / description
        Removed value: -"Announced name of the reader's participant."
      • removedOutput schema / $defs / NotMatchedPair / properties / reader_participant_name / title
        Removed value: -"Reader Participant Name"
      • changedOutput schema / $defs / NotMatchedPair / properties / reason / description
        Previous value: -"`partition`: no reader partition matches a writer partition. `type_name`: the endpoints announced different type names. The RxO QoS rules are not evaluated for such a pair."New value: +"`partition` (none in common) or `type_name` (different types); QoS rules are not evaluated."
      • removedOutput schema / $defs / NotMatchedPair / properties / reason / title
        Removed value: -"Reason"
      • addedOutput schema / $defs / NotMatchedPair / properties / suggested_fixes
        Added value: +{
        +  "description": "Fixes proposed as text for a human to apply; TopicForge never applies them. Only for a `partition` split, and only on the first pair of identical splits on a topic. Empty when it proposes none for this finding.",
        +  "items": {
        +    "$ref": "#/$defs/SuggestedFix"
        +  },
        +  "type": "array"
        +}
      • removedOutput schema / $defs / NotMatchedPair / properties / topic / description
        Removed value: -"Topic name both endpoints use."
      • removedOutput schema / $defs / NotMatchedPair / properties / topic / title
        Removed value: -"Topic"
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_guid / description
        Removed value: -"GUID of the writer endpoint."
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_guid / title
        Removed value: -"Writer Guid"
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_participant_guid / description
        Removed value: -"GUID of the writer's participant."
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_participant_guid / title
        Removed value: -"Writer Participant Guid"
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_participant_name / description
        Removed value: -"Announced name of the writer's participant."
      • removedOutput schema / $defs / NotMatchedPair / properties / writer_participant_name / title
        Removed value: -"Writer Participant Name"
      • removedOutput schema / $defs / NotMatchedPair / title
        Removed value: -"NotMatchedPair"
      • removedOutput schema / $defs / PolicyMismatch / description
        Removed value: -"One QoS policy of a reader/writer pair, with the values that were compared."
      • removedOutput schema / $defs / PolicyMismatch / properties / offered / description
        Removed value: -"What the writer offers, same format as `requested`."
      • removedOutput schema / $defs / PolicyMismatch / properties / offered / title
        Removed value: -"Offered"
      • changedOutput schema / $defs / PolicyMismatch / properties / policy / description
        Previous value: -"Policy name: `Reliability`, `Durability`, `Deadline`, `Liveliness`, `LatencyBudget`, `Ownership`, `DestinationOrder`, `DataRepresentation` or `History`."New value: +"Policy name, e.g. `Reliability`, `Deadline`, `Ownership`, `History`."
      • removedOutput schema / $defs / PolicyMismatch / properties / policy / title
        Removed value: -"Policy"
      • changedOutput schema / $defs / PolicyMismatch / properties / requested / description
        Previous value: -"What the reader requests, human-readable: `\"RELIABLE\"`, `\"100 ms\"`, `\"infinite\"`."New value: +"What the reader requests, e.g. `\"RELIABLE\"`, `\"100 ms\"`."
      • removedOutput schema / $defs / PolicyMismatch / properties / requested / title
        Removed value: -"Requested"
      • removedOutput schema / $defs / PolicyMismatch / properties / rule / title
        Removed value: -"Rule"
      • removedOutput schema / $defs / PolicyMismatch / title
        Removed value: -"PolicyMismatch"
      • addedOutput schema / $defs / SuggestedFix
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "kind": {
        +      "description": "Form of `text`: a CLI command, a ROS 2 QoS override, a DDS profile or a launch parameter.",
        +      "enum": [
        +        "cli",
        +        "qos_override",
        +        "dds_profile",
        +        "launch_param"
        +      ],
        +      "type": "string"
        +    },
        +    "target": {
        +      "description": "Which node or side to change, writer or reader.",
        +      "type": "string"
        +    },
        +    "text": {
        +      "description": "The exact command, YAML or XML to apply.",
        +      "type": "string"
        +    },
        +    "tradeoff": {
        +      "description": "What is lost by applying it.",
        +      "type": "string"
        +    },
        +    "verify_with": {
        +      "description": "The TopicForge tool to run again to confirm the repair.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "kind",
        +    "target",
        +    "text",
        +    "tradeoff",
        +    "verify_with"
        +  ],
        +  "type": "object"
        +}
      • changedOutput schema / properties / hints / description
        Previous value: -"Leads that are not findings: orphan topics with near-identical names (typos), type id differences, pairs that could not be fully checked."New value: +"Leads that are not findings: probable typos, type id notes, incomplete checks."
      • removedOutput schema / properties / hints / title
        Removed value: -"Hints"
      • changedOutput schema / properties / matched / description
        Previous value: -"Pairs that will be matched by DDS given the announced QoS: same topic and type name, overlapping partitions, no incompatible RxO policy (a pair with only a `risky` History finding still counts). Actual data flow is not observed."New value: +"Pairs DDS will connect given the announced QoS (data flow is not observed)."
      • removedOutput schema / properties / matched / title
        Removed value: -"Matched"
      • removedOutput schema / properties / matched_total / title
        Removed value: -"Matched Total"
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • changedOutput schema / properties / not_matched / description
        Previous value: -"Pairs separated by partition or type name. No data flows between them. An empty `reports` with a non-empty `not_matched` does not mean the bus is healthy."New value: +"Pairs DDS never matches (partition or type name). Non-empty with empty `reports` is NOT all-clear: no data flows between them."
      • removedOutput schema / properties / not_matched / title
        Removed value: -"Not Matched"
      • removedOutput schema / properties / not_matched_total / title
        Removed value: -"Not Matched Total"
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One sentence of context, or `None`."
        +}
      • changedOutput schema / properties / pairs_checked / description
        Previous value: -"Same-topic (reader, writer) pairs examined, including those reported in `not_matched`."New value: +"Same-topic (reader, writer) pairs examined."
      • removedOutput schema / properties / pairs_checked / title
        Removed value: -"Pairs Checked"
      • removedOutput schema / properties / policies_checked / title
        Removed value: -"Policies Checked"
      • changedOutput schema / properties / policies_unchecked / description
        Previous value: -"Policies and facts this scan does not cover, each with a one-line reason. A clean result says nothing about them."New value: +"What the scan does not cover, each with a reason; a clean result says nothing about them."
      • removedOutput schema / properties / policies_unchecked / title
        Removed value: -"Policies Unchecked"
      • changedOutput schema / properties / reports / description
        Previous value: -"Pairs that share a partition and a type but have incompatible or risky QoS."New value: +"Pairs sharing partition and type with incompatible or risky QoS."
      • removedOutput schema / properties / reports / title
        Removed value: -"Reports"
      • removedOutput schema / properties / reports_total / title
        Removed value: -"Reports Total"
      • addedOutput schema / properties / summary
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One-line reading of the scan: counts of incompatible, not matched and matched pairs."
        +}
      • removedOutput schema / properties / topics_scanned / title
        Removed value: -"Topics Scanned"
      • changedOutput schema / properties / truncated / description
        Previous value: -"True when `reports`, `matched` or `not_matched` was cut to its cap (200 entries each, incompatible reports first): see the `*_total` fields. Narrow the scan with `topic`."New value: +"True when `reports`, `matched` or `not_matched` hit the 200-entry cap (see `*_total`); narrow with `topic`."
      • removedOutput schema / properties / truncated / title
        Removed value: -"Truncated"
    • Addedget_node_info
    • Changedget_topic_info28 fields changed
      • changedInput schema / properties / topic / description
        Previous value: -"Fully qualified ROS2 topic name starting with `/`, e.g. `/cmd_vel` or `/camera/image_raw`. Each `/`-separated segment must start with a letter or underscore and contain only letters, digits, and underscores; everything else (whitespace, quotes, shell metacharacters, `//`, trailing `/`) is rejected before reaching the `ros2` CLI."New value: +"Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI."
      • removedInput schema / properties / topic / title
        Removed value: -"Topic"
      • removedInput schema / title
        Removed value: -"get_topic_infoArguments"
      • removedOutput schema / $defs / QosProfile
        Removed value: -{
        -  "additionalProperties": false,
        -  "description": "QoS profile of one DDS endpoint (reader or writer).\n\nCovers the policies behind most \"subscriber receives nothing\" cases.\nVendor-specific extensions are ignored.",
        -  "properties": {
        -    "data_representation": {
        -      "anyOf": [
        -        {
        -          "items": {
        -            "type": "string"
        -          },
        -          "type": "array"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Data representations the endpoint accepts or offers, from `XCDR1` and `XCDR2`. `None` when not announced.",
        -      "title": "Data Representation"
        -    },
        -    "deadline_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Deadline QoS in nanoseconds. `None` means no deadline (infinite, the DDS default). A reader deadline tighter (smaller) than a writer deadline is incompatible: the writer cannot guarantee the reader's promise.",
        -      "title": "Deadline Ns"
        -    },
        -    "destination_order": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "BY_RECEPTION_TIMESTAMP",
        -            "BY_SOURCE_TIMESTAMP"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "DestinationOrder QoS. `None` when not announced.",
        -      "title": "Destination Order"
        -    },
        -    "durability": {
        -      "description": "DDS Durability QoS. `VOLATILE` writers do not retain samples for late joiners; `TRANSIENT_LOCAL` writers do. A `TRANSIENT_LOCAL` reader cannot match a `VOLATILE` writer.",
        -      "enum": [
        -        "VOLATILE",
        -        "TRANSIENT_LOCAL",
        -        "TRANSIENT",
        -        "PERSISTENT"
        -      ],
        -      "title": "Durability",
        -      "type": "string"
        -    },
        -    "history": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "KEEP_LAST",
        -            "KEEP_ALL"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "DDS History QoS. `KEEP_LAST` keeps a bounded ring buffer of size `history_depth`; `KEEP_ALL` keeps every sample (memory permitting). Mixed `KEEP_ALL` reader with `KEEP_LAST` writer is risky but not strictly incompatible. `None` when it is not known: DDS discovery does not carry History, so it is reported only for TopicForge's own endpoints and for Cyclone DDS peers that set something other than the default (see `history_note`).",
        -      "title": "History"
        -    },
        -    "history_depth": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Depth for `KEEP_LAST`. `None` for `KEEP_ALL` or when `history` is `None`.",
        -      "title": "History Depth"
        -    },
        -    "history_note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Set when `history` was left out because discovery did not announce it.",
        -      "title": "History Note"
        -    },
        -    "latency_budget_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "LatencyBudget QoS in nanoseconds (a delivery-delay hint). `0` is the DDS default. `None` means infinite or not announced.",
        -      "title": "Latency Budget Ns"
        -    },
        -    "liveliness_kind": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "AUTOMATIC",
        -            "MANUAL_BY_PARTICIPANT",
        -            "MANUAL_BY_TOPIC"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Liveliness QoS kind. `AUTOMATIC` is asserted by the middleware; the two `MANUAL_*` kinds need the application to write or assert liveliness, and the endpoint is declared not alive when it does not within `liveliness_lease_ns`. `None` when not announced.",
        -      "title": "Liveliness Kind"
        -    },
        -    "liveliness_lease_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Liveliness lease duration in nanoseconds. `None` means infinite (the DDS default) or not announced.",
        -      "title": "Liveliness Lease Ns"
        -    },
        -    "ownership_kind": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "SHARED",
        -            "EXCLUSIVE"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Ownership QoS kind. With `EXCLUSIVE`, only the writer with the highest `ownership_strength` delivers samples to a reader. `None` when not announced.",
        -      "title": "Ownership Kind"
        -    },
        -    "ownership_strength": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Ownership strength of a writer (`EXCLUSIVE` ownership). `None` when not announced.",
        -      "title": "Ownership Strength"
        -    },
        -    "partitions": {
        -      "anyOf": [
        -        {
        -          "items": {
        -            "type": "string"
        -          },
        -          "type": "array"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Partition names the endpoint's Publisher or Subscriber belongs to. No Partition policy is the default partition, reported as `[\"\"]` (never an empty list). Endpoints only match when their partitions intersect. `None` only when the backend cannot read partitions at all (unknown, not the default).",
        -      "title": "Partitions"
        -    },
        -    "reliability": {
        -      "description": "DDS Reliability QoS. `RELIABLE` retries lost samples; `BEST_EFFORT` does not. A `RELIABLE` reader cannot match a `BEST_EFFORT` writer.",
        -      "enum": [
        -        "RELIABLE",
        -        "BEST_EFFORT"
        -      ],
        -      "title": "Reliability",
        -      "type": "string"
        -    },
        -    "unknown_policies": {
        -      "anyOf": [
        -        {
        -          "items": {
        -            "type": "string"
        -          },
        -          "type": "array"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Policies the endpoint announced but whose value could not be read (for example `Deadline`). Their duration fields are `None` here, which does NOT mean infinite for these: the scan does not compare them.",
        -      "title": "Unknown Policies"
        -    }
        -  },
        -  "required": [
        -    "reliability",
        -    "durability"
        -  ],
        -  "title": "QosProfile",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / SideQos
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "durability": {
        +      "description": "Shared durability of this side, or `mixed`; `transient_local` marks a latched topic such as `/tf_static`.",
        +      "enum": [
        +        "volatile",
        +        "transient_local",
        +        "mixed"
        +      ],
        +      "type": "string"
        +    },
        +    "endpoint_count": {
        +      "description": "Endpoints of this side the values were read from.",
        +      "minimum": 1,
        +      "type": "integer"
        +    },
        +    "reliability": {
        +      "description": "Reliability shared by the endpoints of this side, or `mixed` when they disagree.",
        +      "enum": [
        +        "reliable",
        +        "best_effort",
        +        "mixed"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "reliability",
        +    "durability",
        +    "endpoint_count"
        +  ],
        +  "type": "object"
        +}
      • changedOutput schema / description
        Previous value: -"Description of a single ROS2 topic.\n\nCarries optional DDS-side enrichment fields when the active middleware\nbackend can resolve them (CycloneDDS / Fast DDS). The ROS2 CLI adapter and\nthe mock ROS2 path leave them `None`."New value: +"Detail of a single ROS2 topic: counts, QoS per side and the nodes on each side."
      • removedOutput schema / properties / message_type / description
        Removed value: -"ROS2 message type, e.g. `geometry_msgs/msg/Twist`."
      • removedOutput schema / properties / message_type / title
        Removed value: -"Message Type"
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • removedOutput schema / properties / name / description
        Removed value: -"Fully qualified topic name, e.g. `/cmd_vel`."
      • removedOutput schema / properties / name / title
        Removed value: -"Name"
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One sentence of context, or `None`."
        +}
      • removedOutput schema / properties / publisher_count / description
        Removed value: -"Publishers known to the graph."
      • removedOutput schema / properties / publisher_count / title
        Removed value: -"Publisher Count"
      • addedOutput schema / properties / publisher_nodes
        Added value: +{
        +  "description": "Publisher node names (`ros2 topic info --verbose`); empty when unknown.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / publisher_qos
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/SideQos"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "QoS of the publishers. `None` when it was not read: see `publisher_qos_note`."
        +}
      • addedOutput schema / properties / publisher_qos_note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Why `publisher_qos` is `None`. `None` when it is set."
        +}
      • removedOutput schema / properties / qos_durability
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Durability announced by the topic's publishers: `volatile`, `transient_local` (late subscribers receive the last samples; typical of latched topics such as `/tf_static`), or `mixed` when publishers disagree. `null` when unknown, with the same rules as `qos_reliability`.",
        -  "title": "Qos Durability"
        -}
      • removedOutput schema / properties / qos_profile
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "$ref": "#/$defs/QosProfile"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Effective DDS QoS profile for this topic when resolvable. `None` from the ROS2 CLI adapter or when the DDS module is inactive. The DDS module populates this on a best-effort basis (picks one representative endpoint if reader/writer QoS differ)."
        -}
      • removedOutput schema / properties / qos_reliability
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Reliability announced by the topic's publishers: `reliable`, `best_effort`, or `mixed` when publishers disagree. `null` when unknown: the topic has no publisher, or the value was not read (`list_topics` does not read QoS; `get_topic_info` does).",
        -  "title": "Qos Reliability"
        -}
      • removedOutput schema / properties / reader_count
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "DDS reader-endpoint count when the active backend can resolve endpoint-level info (Cyclone / Fast DDS). `None` from the ROS2 CLI adapter or when the DDS module is inactive.",
        -  "title": "Reader Count"
        -}
      • removedOutput schema / properties / subscriber_count / description
        Removed value: -"Subscribers known to the graph."
      • removedOutput schema / properties / subscriber_count / title
        Removed value: -"Subscriber Count"
      • addedOutput schema / properties / subscriber_nodes
        Added value: +{
        +  "description": "Subscriber node names; empty when unknown.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / subscription_qos
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/SideQos"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "QoS of the subscriptions. `None` when it was not read: see `subscription_qos_note`."
        +}
      • addedOutput schema / properties / subscription_qos_note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Why `subscription_qos` is `None`. `None` when it is set."
        +}
      • removedOutput schema / properties / writer_count
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "minimum": 0,
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "DDS writer-endpoint count when the active backend can resolve endpoint-level info. `None` from the ROS2 CLI adapter or when the DDS module is inactive.",
        -  "title": "Writer Count"
        -}
    • Changedhealth_check50 fields changed
      • removedInput schema / title
        Removed value: -"health_checkArguments"
      • addedOutput schema / properties / contract_version
        Added value: +{
        +  "default": 2,
        +  "description": "Output contract version; changes only when a field is renamed, removed or retyped.",
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • changedOutput schema / properties / dds_backend / description
        Previous value: -"DDS backend of the adapter actually serving requests. `none` when the DDS module is not active (default for ROS2-only installs). `mock` for synthetic fixtures. `cyclone` requires `pip install \"topicforge[dds-cyclone]\"` (Eclipse CycloneDDS); `fast` requires a Fast DDS Python binding built from eProsima sources (not on PyPI); `opendds` and `dust` are permanent stub adapters that never serve."New value: +"DDS backend serving: `mock`, `cyclone`, `fast`, or `none` (DDS module inactive)."
      • changedOutput schema / properties / dds_backend / enum
        Previous value: -[
        -  "mock",
        -  "cyclone",
        -  "fast",
        -  "opendds",
        -  "dust",
        -  "none"
        -]New value: +[
        +  "mock",
        +  "cyclone",
        +  "fast",
        +  "none"
        +]
      • removedOutput schema / properties / dds_backend / title
        Removed value: -"Dds Backend"
      • removedOutput schema / properties / dds_domain_id / title
        Removed value: -"Dds Domain Id"
      • addedOutput schema / properties / dds_inactive_note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Why `dds_backend` is `none` while ROS 2 serves (not selected, binding missing, or failed to start); else `null`."
        +}
      • removedOutput schema / properties / dds_inactive_reason
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Why `dds_backend` is `none` while the ROS 2 CLI serves: the backend was not selected (`TOPICFORGE_DDS_BACKEND` unset or `mock`), its Python binding is not installed, or the binding is installed but the adapter failed to start. `null` when a DDS backend is serving or the cause is not known.",
        -  "title": "Dds Inactive Reason"
        -}
      • changedOutput schema / properties / dds_security / description
        Previous value: -"DDS Security is not handled. On a secured domain TopicForge can show participants but not protected endpoints or data."New value: +"DDS Security is not handled: protected endpoints and data are invisible."
      • removedOutput schema / properties / dds_security / title
        Removed value: -"Dds Security"
      • changedOutput schema / properties / max_sample_count / description
        Previous value: -"Server-side cap on the number of samples returned per `sample_messages` call. Requests above this limit are silently clamped; the value is exposed here so a client can size its requests proactively. Constant within a given server version."New value: +"Server cap on samples per call; larger requests are clamped."
      • removedOutput schema / properties / max_sample_count / title
        Removed value: -"Max Sample Count"
      • changedOutput schema / properties / middleware_available / description
        Previous value: -"True when a DDS backend is serving (`dds_backend` is not `none`). When the DDS module is inactive (`dds_backend == 'none'`), whether the *configured* backend's Python bindings are importable, so a missing binding is visible."New value: +"True when a DDS backend serves; else whether the configured binding is importable."
      • removedOutput schema / properties / middleware_available / title
        Removed value: -"Middleware Available"
      • changedOutput schema / properties / mode / description
        Previous value: -"Runtime mode of the adapter actually serving requests: `mock` or `live`. `live` with `ros_backend` `none` means the DDS tools are live and the ROS 2 tools are not available. Can differ from `requested_mode` when a live backend could not start (e.g. `live` requested without `ros2` installed falls back to `mock`)."New value: +"Mode actually serving: `mock` or `live` (`live` with `ros_backend` `none`: DDS tools only). May differ from `requested_mode` after a fallback."
      • removedOutput schema / properties / mode / title
        Removed value: -"Mode"
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One sentence of context, or `None`."
        +}
      • removedOutput schema / properties / now_ns / title
        Removed value: -"Now Ns"
      • changedOutput schema / properties / observed_domain_note / description
        Previous value: -"Plain statement of which DDS domain is observed, set when a DDS module is active: only the domain joined at startup is visible, a program on another domain is invisible."New value: +"Which DDS domain is observed: only the one joined at startup; others are invisible."
      • removedOutput schema / properties / observed_domain_note / title
        Removed value: -"Observed Domain Note"
      • changedOutput schema / properties / observer_started_ns / description
        Previous value: -"Wall-clock time (ns since epoch) when the DDS observer joined the bus. Nothing earlier than this was watched: `now_ns` minus this is how long TopicForge has been observing. `None` without a live DDS observer."New value: +"ns when the DDS observer joined; nothing earlier was watched. `None` without one."
      • removedOutput schema / properties / observer_started_ns / title
        Removed value: -"Observer Started Ns"
      • changedOutput schema / properties / payload_decoding / description
        Previous value: -"Whether DDS user-topic payloads are decoded. `disabled` today: `peek_dds_samples` and `topic_metrics` do not return message content for user topics."New value: +"`disabled`: DDS user-topic payloads are not decoded."
      • removedOutput schema / properties / payload_decoding / title
        Removed value: -"Payload Decoding"
      • addedOutput schema / properties / payload_decoding_note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": "user-topic payload decoding is switched off until it is validated on a real bus; builtin discovery topics are still readable",
        +  "description": "Reason for `payload_decoding`."
        +}
      • removedOutput schema / properties / payload_decoding_reason
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": "user-topic payload decoding is switched off until it is validated on a real bus; builtin discovery topics are still readable",
        -  "description": "One-line reason for `payload_decoding`.",
        -  "title": "Payload Decoding Reason"
        -}
      • changedOutput schema / properties / requested_mode / description
        Previous value: -"Mode requested via configuration (may be `auto`)."New value: +"Mode requested by configuration (may be `auto`)."
      • removedOutput schema / properties / requested_mode / title
        Removed value: -"Requested Mode"
      • addedOutput schema / properties / rmw_implementation
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "ROS 2 middleware, e.g. `rmw_fastrtps_cpp`; `None` when `rmw_source` is `none`."
        +}
      • addedOutput schema / properties / rmw_source
        Added value: +{
        +  "default": "none",
        +  "description": "`env`, `ros2_cli`, `distro_default` (not verified on the graph) or `none`.",
        +  "enum": [
        +    "env",
        +    "ros2_cli",
        +    "distro_default",
        +    "none"
        +  ],
        +  "type": "string"
        +}
      • removedOutput schema / properties / ros2_available / title
        Removed value: -"Ros2 Available"
      • changedOutput schema / properties / ros2_distro / description
        Previous value: -"Value of `ROS_DISTRO` if set in the environment. **Env disclosure, by design**: under the local-trust threat model (see README 'Security model'), the MCP client is a trusted agent on a machine the user controls, and exposing the ROS2 distro lets it adapt to e.g. `humble`/`jazzy` differences. For a hosted multi-tenant TopicForge endpoint this field would be scrubbed ."New value: +"`ROS_DISTRO` if set (disclosed by design under the local-trust model)."
      • removedOutput schema / properties / ros2_distro / title
        Removed value: -"Ros2 Distro"
      • changedOutput schema / properties / ros_backend / description
        Previous value: -"Active ROS2 backend. `ros2_cli` when the `ros2` CLI is on PATH and live mode resolves to a Ros2CliAdapter (alone or as the ROS half of a composite). `mock` when MockAdapter serves the ROS surface. `none` when no ROS2 backend is active (e.g. DDS-only live install with no `ros2` CLI). Together with `dds_backend` it tells the ROS2 and DDS halves of the runtime apart."New value: +"ROS 2 backend: `ros2_cli`, `mock` or `none`."
      • removedOutput schema / properties / ros_backend / title
        Removed value: -"Ros Backend"
      • changedOutput schema / properties / ros_tools_available / description
        Previous value: -"True when the ROS 2 tools (`list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag`, `peek_bag_samples`) can run, i.e. `ros_backend` is not `none`. False on a DDS-only setup: use `list_endpoints` for topics and wiring there."New value: +"True when the ROS 2 tools can run (`ros_backend` is not `none`); on a DDS-only setup use `list_endpoints`."
      • removedOutput schema / properties / ros_tools_available / title
        Removed value: -"Ros Tools Available"
      • changedOutput schema / properties / server_version / description
        Previous value: -"TopicForge server version (matches the PyPI release of the `topicforge` package)."New value: +"TopicForge version."
      • removedOutput schema / properties / server_version / title
        Removed value: -"Server Version"
      • changedOutput schema / properties / sim_clock_published / description
        Previous value: -"Whether `/clock` has at least one publisher on the ROS 2 graph. True suggests nodes may run on simulated time (`use_sim_time`), so `header.stamp` values are sim time, not wall time (compare with `received_ns`). It is a hint: a publisher on `/clock` does not prove a given node follows it. `None` in mock mode or when the `ros2` CLI could not answer."New value: +"Whether `/clock` had a publisher in the last graph read (a hint that stamps may be sim time). `None` in mock or without a recent read; `health_check` never runs `ros2`."
      • removedOutput schema / properties / sim_clock_published / title
        Removed value: -"Sim Clock Published"
      • changedOutput schema / properties / tracker_cache_evictions / description
        Previous value: -"Discovery entries dropped because a tracker cache was full (4096 per cache). Non-zero means the bus is bigger than what is listed."New value: +"Discovery entries dropped from a full tracker cache (4096 each); non-zero means the bus is bigger than listed."
      • removedOutput schema / properties / tracker_cache_evictions / title
        Removed value: -"Tracker Cache Evictions"
      • changedOutput schema / properties / tracker_errors / description
        Previous value: -"Discovery tracker passes that raised (swallowed and logged). Non-zero means gaps."New value: +"Tracker passes that raised; non-zero means gaps."
      • removedOutput schema / properties / tracker_errors / title
        Removed value: -"Tracker Errors"
      • changedOutput schema / properties / tracker_last_pass_ns / description
        Previous value: -"Wall-clock time (ns since epoch) of the last completed tracker pass. A value far older than `now_ns` means lifecycle is stale."New value: +"ns of the last tracker pass; far older than `now_ns` means stale lifecycle."
      • removedOutput schema / properties / tracker_last_pass_ns / title
        Removed value: -"Tracker Last Pass Ns"
      • removedOutput schema / properties / tracker_passes / title
        Removed value: -"Tracker Passes"
      • changedOutput schema / properties / tracker_running / description
        Previous value: -"Whether the continuous discovery tracker thread is alive (Cyclone). `None` when the backend has no tracker."New value: +"Whether the discovery tracker thread is alive (Cyclone); `None` without a tracker."
      • removedOutput schema / properties / tracker_running / title
        Removed value: -"Tracker Running"
    • Changedlist_endpoints125 fields changed
      • changedInput schema / properties / domain_id / description
        Previous value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value."
      • removedInput schema / properties / domain_id / title
        Removed value: -"Domain Id"
      • addedInput schema / properties / fields
        Added value: +{
        +  "default": "full",
        +  "description": "`full` (default) or `compact`: one-line `qos_summary` instead of the full `qos`, no `type_id`, `announced_ns` or `ros_topic_note`. Use `compact` on large buses.",
        +  "enum": [
        +    "full",
        +    "compact"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / properties / include_departed / description
        Previous value: -"Also list endpoints whose participant left the bus (flagged with `gone_ns`). Defaults to false; `by_topic` reports them as `departed_writers` / `departed_readers` either way."New value: +"Also list endpoints whose participant left (flagged with `gone_ns`); `by_topic` reports them either way. Default false."
      • removedInput schema / properties / include_departed / title
        Removed value: -"Include Departed"
      • addedInput schema / properties / include_internal
        Added value: +{
        +  "default": false,
        +  "description": "Also list ROS 2 service and action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`), most of a small robot's endpoints. Default false.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / include_observer / description
        Previous value: -"Include TopicForge's own observer participant's endpoints. Defaults to false."New value: +"Include TopicForge's own observer endpoints. Default false."
      • removedInput schema / properties / include_observer / title
        Removed value: -"Include Observer"
      • changedInput schema / properties / participant_guid / description
        Previous value: -"Only endpoints owned by this participant, as the guid reported by `list_participants` or by an earlier `list_endpoints`. Omit for all participants."New value: +"Only endpoints of this participant (a guid from `list_participants`). Omit for all."
      • removedInput schema / properties / participant_guid / title
        Removed value: -"Participant Guid"
      • changedInput schema / properties / topic / description
        Previous value: -"Only endpoints on this DDS topic name: a bare name such as `scan` or a ROS 2 mangled name such as `rt/scan` (exact name first, then the alternate form). Omit to list every topic."New value: +"Only endpoints on this DDS topic: `scan` or `rt/scan` (exact name first, then the alternate form). Omit for every topic."
      • removedInput schema / properties / topic / title
        Removed value: -"Topic"
      • removedInput schema / title
        Removed value: -"list_endpointsArguments"
      • removedOutput schema / $defs / DepartedEndpoint / description
        Removed value: -"An endpoint whose participant left the bus (crash, clean exit or lease expiry)."
      • removedOutput schema / $defs / DepartedEndpoint / properties / gone_ns / title
        Removed value: -"Gone Ns"
      • removedOutput schema / $defs / DepartedEndpoint / properties / guid / description
        Removed value: -"GUID of the departed endpoint."
      • removedOutput schema / $defs / DepartedEndpoint / properties / guid / title
        Removed value: -"Guid"
      • removedOutput schema / $defs / DepartedEndpoint / properties / participant_guid / description
        Removed value: -"GUID of the participant that owned it."
      • removedOutput schema / $defs / DepartedEndpoint / properties / participant_guid / title
        Removed value: -"Participant Guid"
      • removedOutput schema / $defs / DepartedEndpoint / properties / participant_name / description
        Removed value: -"Announced name of that participant, `None` when it set none."
      • removedOutput schema / $defs / DepartedEndpoint / properties / participant_name / title
        Removed value: -"Participant Name"
      • removedOutput schema / $defs / DepartedEndpoint / title
        Removed value: -"DepartedEndpoint"
      • removedOutput schema / $defs / EndpointInfo / description
        Removed value: -"One DDS endpoint (writer or reader) announced through discovery."
      • removedOutput schema / $defs / EndpointInfo / properties / activity
        Removed value: -{
        -  "default": null,
        -  "description": "Reserved for a future liveness signal. Always `None` today.",
        -  "title": "Activity",
        -  "type": "null"
        -}
      • removedOutput schema / $defs / EndpointInfo / properties / activity_note
        Removed value: -{
        -  "default": "not observed: TopicForge holds no reader on user topics, so it cannot tell a silent or hung writer from a healthy one",
        -  "description": "Why `activity` is not populated.",
        -  "title": "Activity Note",
        -  "type": "string"
        -}
      • changedOutput schema / $defs / EndpointInfo / properties / announced_ns / description
        Previous value: -"Source timestamp of the discovery announcement, ns since epoch, read on the announcing side's clock (it can differ from this host's clock). `None` when not available."New value: +"Source timestamp of the announcement, on the announcer's clock; `None` if unavailable."
      • removedOutput schema / $defs / EndpointInfo / properties / announced_ns / title
        Removed value: -"Announced Ns"
      • addedOutput schema / $defs / EndpointInfo / properties / dds_topic
        Added value: +{
        +  "type": "string"
        +}
      • removedOutput schema / $defs / EndpointInfo / properties / domain_id
        Removed value: -{
        -  "description": "DDS domain the endpoint was observed on.",
        -  "maximum": 232,
        -  "minimum": 0,
        -  "title": "Domain Id",
        -  "type": "integer"
        -}
      • changedOutput schema / $defs / EndpointInfo / properties / gone_ns / description
        Previous value: -"`None` for a live endpoint. For a departed endpoint (only listed with `include_departed`): when its participant was lost (the participant's `lost_ns`, an upper bound of the death)."New value: +"`None` for a live endpoint; for a departed one (`include_departed`) when its participant was lost (upper bound)."
      • removedOutput schema / $defs / EndpointInfo / properties / gone_ns / title
        Removed value: -"Gone Ns"
      • removedOutput schema / $defs / EndpointInfo / properties / guid / description
        Removed value: -"GUID of the endpoint, `xxxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx`."
      • removedOutput schema / $defs / EndpointInfo / properties / guid / title
        Removed value: -"Guid"
      • removedOutput schema / $defs / EndpointInfo / properties / is_observer / title
        Removed value: -"Is Observer"
      • removedOutput schema / $defs / EndpointInfo / properties / mode_effective
        Removed value: -{
        -  "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.",
        -  "enum": [
        -    "mock",
        -    "live"
        -  ],
        -  "title": "Mode Effective",
        -  "type": "string"
        -}
      • removedOutput schema / $defs / EndpointInfo / properties / participant_guid / description
        Removed value: -"GUID of the owning participant, same format as `list_participants`."
      • removedOutput schema / $defs / EndpointInfo / properties / participant_guid / title
        Removed value: -"Participant Guid"
      • removedOutput schema / $defs / EndpointInfo / properties / participant_name / description
        Removed value: -"Announced name of the owning participant, `None` when it set none."
      • removedOutput schema / $defs / EndpointInfo / properties / participant_name / title
        Removed value: -"Participant Name"
      • removedOutput schema / $defs / EndpointInfo / properties / participant_vendor / description
        Removed value: -"Vendor of the owning participant, same value as `list_participants` (`unknown` when it cannot be determined)."
      • removedOutput schema / $defs / EndpointInfo / properties / participant_vendor / title
        Removed value: -"Participant Vendor"
      • changedOutput schema / $defs / EndpointInfo / properties / qos / description
        Previous value: -"QoS the endpoint announced. Durations (`deadline_ns`, `liveliness_lease_ns`, `latency_budget_ns`) of `None` mean infinite or not set. `None` when reliability or durability could not be resolved."New value: +"Announced QoS; `None` when reliability or durability could not be resolved. A duration of `None` is infinite or unset."
      • addedOutput schema / $defs / EndpointInfo / properties / qos_summary
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One-line QoS, set only with `fields` `compact` (where `qos` is `None`)."
        +}
      • removedOutput schema / $defs / EndpointInfo / properties / role / title
        Removed value: -"Role"
      • addedOutput schema / $defs / EndpointInfo / properties / ros_topic
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "ROS 2 name of the topic, for example `/scan`. `None` when the endpoint is not a ROS 2 topic: see `ros_topic_note`."
        +}
      • addedOutput schema / $defs / EndpointInfo / properties / ros_topic_note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Why `ros_topic` is `None`. `None` when it is set."
        +}
      • removedOutput schema / $defs / EndpointInfo / properties / topic
        Removed value: -{
        -  "description": "DDS topic name.",
        -  "title": "Topic",
        -  "type": "string"
        -}
      • removedOutput schema / $defs / EndpointInfo / properties / type_id / title
        Removed value: -"Type Id"
      • removedOutput schema / $defs / EndpointInfo / properties / type_name / description
        Removed value: -"Announced data type name."
      • removedOutput schema / $defs / EndpointInfo / properties / type_name / title
        Removed value: -"Type Name"
      • changedOutput schema / $defs / EndpointInfo / required
        Previous value: -[
        -  "guid",
        -  "role",
        -  "participant_guid",
        -  "topic",
        -  "is_observer",
        -  "domain_id",
        -  "mode_effective"
        -]New value: +[
        +  "guid",
        +  "role",
        +  "participant_guid",
        +  "dds_topic",
        +  "is_observer"
        +]
      • removedOutput schema / $defs / EndpointInfo / title
        Removed value: -"EndpointInfo"
      • removedOutput schema / $defs / QosProfile / description
        Removed value: -"QoS profile of one DDS endpoint (reader or writer).\n\nCovers the policies behind most \"subscriber receives nothing\" cases.\nVendor-specific extensions are ignored."
      • changedOutput schema / $defs / QosProfile / properties / data_representation / description
        Previous value: -"Data representations the endpoint accepts or offers, from `XCDR1` and `XCDR2`. `None` when not announced."New value: +"`XCDR1` / `XCDR2` accepted or offered; `None` when not announced."
      • removedOutput schema / $defs / QosProfile / properties / data_representation / title
        Removed value: -"Data Representation"
      • changedOutput schema / $defs / QosProfile / properties / deadline_ns / description
        Previous value: -"Deadline QoS in nanoseconds. `None` means no deadline (infinite, the DDS default). A reader deadline tighter (smaller) than a writer deadline is incompatible: the writer cannot guarantee the reader's promise."New value: +"Deadline in ns; `None` is infinite. A reader deadline shorter than the writer's is incompatible."
      • removedOutput schema / $defs / QosProfile / properties / deadline_ns / title
        Removed value: -"Deadline Ns"
      • removedOutput schema / $defs / QosProfile / properties / destination_order / title
        Removed value: -"Destination Order"
      • changedOutput schema / $defs / QosProfile / properties / durability / description
        Previous value: -"DDS Durability QoS. `VOLATILE` writers do not retain samples for late joiners; `TRANSIENT_LOCAL` writers do. A `TRANSIENT_LOCAL` reader cannot match a `VOLATILE` writer."New value: +"`VOLATILE` keeps nothing for late joiners, `TRANSIENT_LOCAL` does; a TRANSIENT_LOCAL reader cannot match a VOLATILE writer."
      • removedOutput schema / $defs / QosProfile / properties / durability / title
        Removed value: -"Durability"
      • changedOutput schema / $defs / QosProfile / properties / history / description
        Previous value: -"DDS History QoS. `KEEP_LAST` keeps a bounded ring buffer of size `history_depth`; `KEEP_ALL` keeps every sample (memory permitting). Mixed `KEEP_ALL` reader with `KEEP_LAST` writer is risky but not strictly incompatible. `None` when it is not known: DDS discovery does not carry History, so it is reported only for TopicForge's own endpoints and for Cyclone DDS peers that set something other than the default (see `history_note`)."New value: +"`KEEP_LAST` (ring of `history_depth`) or `KEEP_ALL`; `None` when discovery did not announce it (see `history_note`)."
      • removedOutput schema / $defs / QosProfile / properties / history / title
        Removed value: -"History"
      • changedOutput schema / $defs / QosProfile / properties / history_depth / description
        Previous value: -"Depth for `KEEP_LAST`. `None` for `KEEP_ALL` or when `history` is `None`."New value: +"Depth for `KEEP_LAST`; else `None`."
      • removedOutput schema / $defs / QosProfile / properties / history_depth / title
        Removed value: -"History Depth"
      • changedOutput schema / $defs / QosProfile / properties / history_note / description
        Previous value: -"Set when `history` was left out because discovery did not announce it."New value: +"Why `history` is `None`."
      • removedOutput schema / $defs / QosProfile / properties / history_note / title
        Removed value: -"History Note"
      • changedOutput schema / $defs / QosProfile / properties / latency_budget_ns / description
        Previous value: -"LatencyBudget QoS in nanoseconds (a delivery-delay hint). `0` is the DDS default. `None` means infinite or not announced."New value: +"LatencyBudget in ns (a hint); `0` is the default, `None` is infinite or not announced."
      • removedOutput schema / $defs / QosProfile / properties / latency_budget_ns / title
        Removed value: -"Latency Budget Ns"
      • changedOutput schema / $defs / QosProfile / properties / liveliness_kind / description
        Previous value: -"Liveliness QoS kind. `AUTOMATIC` is asserted by the middleware; the two `MANUAL_*` kinds need the application to write or assert liveliness, and the endpoint is declared not alive when it does not within `liveliness_lease_ns`. `None` when not announced."New value: +"`AUTOMATIC`, or `MANUAL_*` (the app must assert liveliness within `liveliness_lease_ns`); `None` when not announced."
      • removedOutput schema / $defs / QosProfile / properties / liveliness_kind / title
        Removed value: -"Liveliness Kind"
      • changedOutput schema / $defs / QosProfile / properties / liveliness_lease_ns / description
        Previous value: -"Liveliness lease duration in nanoseconds. `None` means infinite (the DDS default) or not announced."New value: +"Liveliness lease in ns; `None` is infinite or not announced."
      • removedOutput schema / $defs / QosProfile / properties / liveliness_lease_ns / title
        Removed value: -"Liveliness Lease Ns"
      • changedOutput schema / $defs / QosProfile / properties / ownership_kind / description
        Previous value: -"Ownership QoS kind. With `EXCLUSIVE`, only the writer with the highest `ownership_strength` delivers samples to a reader. `None` when not announced."New value: +"With `EXCLUSIVE` only the writer with the highest `ownership_strength` delivers; `None` when not announced."
      • removedOutput schema / $defs / QosProfile / properties / ownership_kind / title
        Removed value: -"Ownership Kind"
      • changedOutput schema / $defs / QosProfile / properties / ownership_strength / description
        Previous value: -"Ownership strength of a writer (`EXCLUSIVE` ownership). `None` when not announced."New value: +"Writer strength under `EXCLUSIVE` ownership; `None` when not announced."
      • removedOutput schema / $defs / QosProfile / properties / ownership_strength / title
        Removed value: -"Ownership Strength"
      • changedOutput schema / $defs / QosProfile / properties / partitions / description
        Previous value: -"Partition names the endpoint's Publisher or Subscriber belongs to. No Partition policy is the default partition, reported as `[\"\"]` (never an empty list). Endpoints only match when their partitions intersect. `None` only when the backend cannot read partitions at all (unknown, not the default)."New value: +"Partition names; the default partition is `[\"\"]`, never `[]`. Endpoints match only if partitions intersect; `None` when unreadable."
      • removedOutput schema / $defs / QosProfile / properties / partitions / title
        Removed value: -"Partitions"
      • changedOutput schema / $defs / QosProfile / properties / reliability / description
        Previous value: -"DDS Reliability QoS. `RELIABLE` retries lost samples; `BEST_EFFORT` does not. A `RELIABLE` reader cannot match a `BEST_EFFORT` writer."New value: +"`RELIABLE` retries lost samples, `BEST_EFFORT` does not; a RELIABLE reader cannot match a BEST_EFFORT writer."
      • removedOutput schema / $defs / QosProfile / properties / reliability / title
        Removed value: -"Reliability"
      • changedOutput schema / $defs / QosProfile / properties / unknown_policies / description
        Previous value: -"Policies the endpoint announced but whose value could not be read (for example `Deadline`). Their duration fields are `None` here, which does NOT mean infinite for these: the scan does not compare them."New value: +"Policies announced but unreadable (e.g. `Deadline`): their fields are `None`, which does NOT mean infinite; the scan does not compare them."
      • removedOutput schema / $defs / QosProfile / properties / unknown_policies / title
        Removed value: -"Unknown Policies"
      • removedOutput schema / $defs / QosProfile / title
        Removed value: -"QosProfile"
      • removedOutput schema / $defs / TopicSummary / description
        Removed value: -"Per-topic roll-up of the listed endpoints, for spotting orphans."
      • addedOutput schema / $defs / TopicSummary / properties / dds_topic
        Added value: +{
        +  "type": "string"
        +}
      • changedOutput schema / $defs / TopicSummary / properties / departed_readers / description
        Previous value: -"Readers on this topic whose participant left, newest first."New value: +"Readers whose participant left, newest first."
      • removedOutput schema / $defs / TopicSummary / properties / departed_readers / title
        Removed value: -"Departed Readers"
      • changedOutput schema / $defs / TopicSummary / properties / departed_writers / description
        Previous value: -"Writers on this topic whose participant left, newest first (bounded memory: last 200 departed endpoints, 1 h). Explains a topic that lost its only writer."New value: +"Writers whose participant left, newest first (last 200, 1 h)."
      • removedOutput schema / $defs / TopicSummary / properties / departed_writers / title
        Removed value: -"Departed Writers"
      • changedOutput schema / $defs / TopicSummary / properties / orphan / description
        Previous value: -"`no_reader`: writers but no reader. `no_writer`: readers but no writer. `None` when both sides exist."New value: +"`no_reader` (writers only), `no_writer` (readers only), else `None`."
      • removedOutput schema / $defs / TopicSummary / properties / orphan / title
        Removed value: -"Orphan"
      • removedOutput schema / $defs / TopicSummary / properties / partitions / title
        Removed value: -"Partitions"
      • removedOutput schema / $defs / TopicSummary / properties / reader_count / description
        Removed value: -"Number of listed readers."
      • removedOutput schema / $defs / TopicSummary / properties / reader_count / title
        Removed value: -"Reader Count"
      • addedOutput schema / $defs / TopicSummary / properties / ros_topic
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "ROS 2 name of the topic, for example `/scan`. `None` for a DDS topic that is not a ROS 2 topic."
        +}
      • removedOutput schema / $defs / TopicSummary / properties / topic
        Removed value: -{
        -  "description": "DDS topic name.",
        -  "title": "Topic",
        -  "type": "string"
        -}
      • removedOutput schema / $defs / TopicSummary / properties / type_names / description
        Removed value: -"Distinct type names announced on this topic."
      • removedOutput schema / $defs / TopicSummary / properties / type_names / title
        Removed value: -"Type Names"
      • removedOutput schema / $defs / TopicSummary / properties / writer_count / description
        Removed value: -"Number of listed writers."
      • removedOutput schema / $defs / TopicSummary / properties / writer_count / title
        Removed value: -"Writer Count"
      • changedOutput schema / $defs / TopicSummary / required
        Previous value: -[
        -  "topic",
        -  "type_names",
        -  "writer_count",
        -  "reader_count",
        -  "partitions"
        -]New value: +[
        +  "dds_topic",
        +  "type_names",
        +  "writer_count",
        +  "reader_count",
        +  "partitions"
        +]
      • removedOutput schema / $defs / TopicSummary / title
        Removed value: -"TopicSummary"
      • removedOutput schema / properties / by_topic / title
        Removed value: -"By Topic"
      • addedOutput schema / properties / departed_endpoint_count
        Added value: +{
        +  "default": 0,
        +  "description": "Departed endpoints matching the filters; listed in `endpoints` only with `include_departed`.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • removedOutput schema / properties / departed_endpoints
        Removed value: -{
        -  "default": 0,
        -  "description": "Departed endpoints (their participant left) matching the filters. They are in `endpoints` only with `include_departed`; `by_topic` always carries them as `departed_writers` / `departed_readers`.",
        -  "minimum": 0,
        -  "title": "Departed Endpoints",
        -  "type": "integer"
        -}
      • removedOutput schema / properties / domain_id / description
        Removed value: -"DDS domain observed."
      • removedOutput schema / properties / domain_id / title
        Removed value: -"Domain Id"
      • removedOutput schema / properties / endpoints / title
        Removed value: -"Endpoints"
      • addedOutput schema / properties / excluded_observer_endpoint_count
        Added value: +{
        +  "default": 0,
        +  "description": "TopicForge's own endpoints left out of `endpoints` (counted in `total`).",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • removedOutput schema / properties / excluded_observer_endpoints
        Removed value: -{
        -  "default": 0,
        -  "description": "Endpoints of TopicForge's own observer participant left out of `endpoints` (they are counted in `total_discovered`). Explains `total_discovered` vs `returned` together with the filters.",
        -  "minimum": 0,
        -  "title": "Excluded Observer Endpoints",
        -  "type": "integer"
        -}
      • addedOutput schema / properties / fields
        Added value: +{
        +  "default": "full",
        +  "description": "Detail level served: `compact` drops per-endpoint QoS detail, type id and notes.",
        +  "enum": [
        +    "full",
        +    "compact"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / hidden_internal_endpoint_count
        Added value: +{
        +  "default": 0,
        +  "description": "Service/action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`) left out while `include_internal` is false.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / hints
        Added value: +{
        +  "description": "Facts true of every endpoint, stated once (e.g. liveness is not observed).",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • changedOutput schema / properties / note / description
        Previous value: -"Hint when a `topic` filter matched nothing: names the closest known topics."New value: +"Set when a `topic` filter matched nothing: the closest known topics."
      • removedOutput schema / properties / note / title
        Removed value: -"Note"
      • removedOutput schema / properties / observer_guid / title
        Removed value: -"Observer Guid"
      • removedOutput schema / properties / returned / description
        Removed value: -"Length of `endpoints`."
      • removedOutput schema / properties / returned / title
        Removed value: -"Returned"
      • removedOutput schema / properties / snapshot_ns / title
        Removed value: -"Snapshot Ns"
      • addedOutput schema / properties / total
        Added value: +{
        +  "description": "Endpoints in the discovery cache before any filter.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • removedOutput schema / properties / total_discovered
        Removed value: -{
        -  "description": "Endpoints in the discovery cache before any filter.",
        -  "minimum": 0,
        -  "title": "Total Discovered",
        -  "type": "integer"
        -}
      • removedOutput schema / properties / truncated / title
        Removed value: -"Truncated"
      • changedOutput schema / required
        Previous value: -[
        -  "domain_id",
        -  "snapshot_ns",
        -  "endpoints",
        -  "by_topic",
        -  "total_discovered",
        -  "returned",
        -  "truncated",
        -  "mode_effective"
        -]New value: +[
        +  "domain_id",
        +  "snapshot_ns",
        +  "endpoints",
        +  "by_topic",
        +  "total",
        +  "returned",
        +  "truncated",
        +  "mode_effective"
        +]
    • Addedlist_nodes
    • Changedlist_participants52 fields changed
      • changedInput schema / properties / domain_id / description
        Previous value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value."
      • removedInput schema / properties / domain_id / title
        Removed value: -"Domain Id"
      • removedInput schema / title
        Removed value: -"list_participantsArguments"
      • addedOutput schema / $defs / NameLifecycle
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "departures": {
        +      "description": "Times a participant with this name was lost.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "lives": {
        +      "description": "Distinct GUIDs that carried this name; restarts = `lives` - 1.",
        +      "minimum": 1,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "lives",
        +    "departures"
        +  ],
        +  "type": "object"
        +}
      • removedOutput schema / $defs / ParticipantInfo / description
        Removed value: -"DDS participant discovered on the configured domain.\n\nCarries identity (`guid`, `name`, `vendor`) plus lifecycle fields\n(`first_seen_ns`, `last_seen_ns`, `status`, `seen_count`)."
      • changedOutput schema / $defs / ParticipantInfo / properties / announced_ns / description
        Previous value: -"Nanoseconds since epoch (DDS source timestamp of the participant's most recent announcement, taken from the announcing side's clock; the local clock when the announcer sent none). `None` when the backend does not expose it. Distinct from `first_seen_ns` / `last_seen_ns`, which are TopicForge's own local clock."New value: +"Source timestamp of the latest announcement, on the announcer's clock; `None` if unavailable."
      • removedOutput schema / $defs / ParticipantInfo / properties / announced_ns / title
        Removed value: -"Announced Ns"
      • removedOutput schema / $defs / ParticipantInfo / properties / domain_id / description
        Removed value: -"DDS domain id the participant is bound to."
      • removedOutput schema / $defs / ParticipantInfo / properties / domain_id / title
        Removed value: -"Domain Id"
      • changedOutput schema / $defs / ParticipantInfo / properties / first_seen_ns / description
        Previous value: -"Wall-clock timestamp (nanoseconds since epoch) of the **first** discovery sample TopicForge observed for this participant. `None` when the adapter does not track lifecycle."New value: +"Wall-clock ns of the first discovery sample seen; `None` without lifecycle tracking."
      • removedOutput schema / $defs / ParticipantInfo / properties / first_seen_ns / title
        Removed value: -"First Seen Ns"
      • removedOutput schema / $defs / ParticipantInfo / properties / guid / description
        Removed value: -"DDS Global Unique Identifier of the participant: hex string, stable across discovery events within a single deployment."
      • removedOutput schema / $defs / ParticipantInfo / properties / guid / title
        Removed value: -"Guid"
      • removedOutput schema / $defs / ParticipantInfo / properties / hostname / description
        Removed value: -"Hostname announced in discovery (Cyclone `__Hostname` property). `None` when the vendor does not announce it."
      • removedOutput schema / $defs / ParticipantInfo / properties / hostname / title
        Removed value: -"Hostname"
      • changedOutput schema / $defs / ParticipantInfo / properties / is_observer / description
        Previous value: -"True for TopicForge's own read-only observer participant, which joins the domain to watch it and appears in this list; false for every other participant."New value: +"True for TopicForge's own read-only observer participant."
      • removedOutput schema / $defs / ParticipantInfo / properties / is_observer / title
        Removed value: -"Is Observer"
      • changedOutput schema / $defs / ParticipantInfo / properties / last_seen_ns / description
        Previous value: -"Wall-clock timestamp (nanoseconds since epoch) of the **most recent** discovery sample observed. Used by `participant_events` to distinguish currently-active participants from those that left the bus. `None` when the adapter does not track lifecycle."New value: +"Wall-clock ns of the latest discovery sample; `None` without lifecycle tracking."
      • removedOutput schema / $defs / ParticipantInfo / properties / last_seen_ns / title
        Removed value: -"Last Seen Ns"
      • changedOutput schema / $defs / ParticipantInfo / properties / lost_ns / description
        Previous value: -"Nanoseconds since epoch of the moment the participant left, once `status` is `left`; `None` while it is active. How precise it is depends on `lost_time_source`. It is an upper bound of when the participant died: after a clean shutdown it is the exact leave time, after a crash it is when the lease expired, and the two cannot be told apart. The process died at or before `lost_ns`, and at most one lease earlier (10 s Cyclone default, 20 s Fast, 100 s RTI)."New value: +"Once `left`: ns upper bound of the death (exact after a clean shutdown; after a crash the lease expiry, up to one lease earlier: 10 s Cyclone, 20 s Fast, 100 s RTI). `None` while active."
      • removedOutput schema / $defs / ParticipantInfo / properties / lost_ns / title
        Removed value: -"Lost Ns"
      • changedOutput schema / $defs / ParticipantInfo / properties / lost_time_source / description
        Previous value: -"Where `lost_ns` comes from: `dds_source_timestamp` (timestamp carried by the discovery dispose) or `observed_local` (the moment TopicForge noticed, the weakest). `None` while active."New value: +"Origin of `lost_ns`: `dds_source_timestamp` or `observed_local` (weakest); `None` while active."
      • removedOutput schema / $defs / ParticipantInfo / properties / lost_time_source / title
        Removed value: -"Lost Time Source"
      • removedOutput schema / $defs / ParticipantInfo / properties / mode_effective
        Removed value: -{
        -  "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.",
        -  "enum": [
        -    "mock",
        -    "live"
        -  ],
        -  "title": "Mode Effective",
        -  "type": "string"
        -}
      • changedOutput schema / $defs / ParticipantInfo / properties / name / description
        Previous value: -"Participant name the application announced through the standard DDS EntityName QoS (PID_ENTITY_NAME in RTPS discovery), for example `lidar_driver`. `None` when the application did not set one (Dust DDS cannot) or the backend does not expose it."New value: +"Announced EntityName (e.g. `lidar_driver`); `None` when the application set none (Dust DDS cannot)."
      • removedOutput schema / $defs / ParticipantInfo / properties / name / title
        Removed value: -"Name"
      • addedOutput schema / $defs / ParticipantInfo / properties / node_names
        Added value: +{
        +  "description": "Fully qualified ROS 2 node names sharing this participant; empty when `node_names_source` is `none`.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / $defs / ParticipantInfo / properties / node_names_source
        Added value: +{
        +  "default": "none",
        +  "description": "`ros_discovery_info`, `ros2_cli` or `none` (no link made).",
        +  "enum": [
        +    "ros_discovery_info",
        +    "ros2_cli",
        +    "none"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / $defs / ParticipantInfo / properties / seen_count / description
        Previous value: -"Number of distinct discovery samples observed for this participant across all calls to `list_participants` during this server's lifetime. Starts at 1 and increments on each observation."New value: +"Discovery observations of this GUID in this server's lifetime. Not a restart count: a restart is a new GUID (see `restarts_by_name`)."
      • removedOutput schema / $defs / ParticipantInfo / properties / seen_count / title
        Removed value: -"Seen Count"
      • changedOutput schema / $defs / ParticipantInfo / properties / status / description
        Previous value: -"Lifecycle status. `active` means TopicForge has a recent discovery sample for this GUID and the bus has not signalled removal. `left` means the participant was observed earlier but has since disappeared (a Fast DDS `REMOVED` callback or a Cyclone polling delta). `unknown` when the adapter does not track lifecycle."New value: +"`active`, `left` (seen earlier, since gone) or `unknown` (no lifecycle tracking)."
      • removedOutput schema / $defs / ParticipantInfo / properties / status / title
        Removed value: -"Status"
      • changedOutput schema / $defs / ParticipantInfo / properties / vendor / description
        Previous value: -"DDS implementation that announced this participant, decoded from the OMG-RTPS `vendor_id` field on the discovery sample. `cyclone` (Eclipse Cyclone DDS), `fast` (eProsima Fast DDS), `rti` (RTI Connext), `rti_micro` (RTI Connext Micro), `opensplice` (ADLink OpenSplice), `opendds` (OCI OpenDDS), `coredx` (Twin Oaks CoreDX), `intercom` (Kongsberg InterCOM), `dust` (S2E Dust DDS). `mock` is reserved for synthetic fixtures. `unknown` means the vendor could not be determined: TopicForge reads it from the vendor prefix of the participant GUID, and some vendors (for example Dust DDS and RTI Connext) do not put their vendor id there, while the Cyclone Python binding does not expose the RTPS header vendor id. The participant is still reported; see `vendor_source`. Any conformant DDS-RTPS participant should appear through standard discovery; Cyclone DDS and Dust DDS have been observed on a live bus. See `docs/dds-interop-matrix.md`."New value: +"DDS implementation read from the GUID vendor prefix: cyclone, fast, rti, rti_micro, opensplice, opendds, coredx, intercom, dust, mock or `unknown` (Dust DDS and RTI Connext do not put theirs there); see `vendor_source`."
      • removedOutput schema / $defs / ParticipantInfo / properties / vendor / title
        Removed value: -"Vendor"
      • changedOutput schema / $defs / ParticipantInfo / properties / vendor_source / description
        Previous value: -"Where `vendor` came from: `guid_prefix` when it was read from the vendor prefix of the participant GUID, `none` when the vendor is unknown (or synthetic in mock mode)."New value: +"`guid_prefix` when `vendor` was read from the GUID, else `none`."
      • removedOutput schema / $defs / ParticipantInfo / properties / vendor_source / title
        Removed value: -"Vendor Source"
      • changedOutput schema / $defs / ParticipantInfo / required
        Previous value: -[
        -  "guid",
        -  "vendor",
        -  "domain_id",
        -  "mode_effective"
        -]New value: +[
        +  "guid",
        +  "vendor",
        +  "domain_id"
        +]
      • removedOutput schema / $defs / ParticipantInfo / title
        Removed value: -"ParticipantInfo"
      • addedOutput schema / additionalProperties
        Added value: +false
      • addedOutput schema / description
        Added value: +"Envelope returned by `list_participants`."
      • addedOutput schema / properties / domain_id
        Added value: +{
        +  "description": "DDS domain observed (joined at startup).",
        +  "maximum": 232,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / mode_effective
        Added value: +{
        +  "description": "`live` or `mock` (fixtures).",
        +  "enum": [
        +    "mock",
        +    "live"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One sentence of context, or `None`."
        +}
      • addedOutput schema / properties / participants
        Added value: +{
        +  "items": {
        +    "$ref": "#/$defs/ParticipantInfo"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / restarts_by_name
        Added value: +{
        +  "additionalProperties": {
        +    "$ref": "#/$defs/NameLifecycle"
        +  },
        +  "description": "Names seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name.",
        +  "type": "object"
        +}
      • addedOutput schema / properties / restarts_note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set when unnamed participants were left out of `restarts_by_name`."
        +}
      • removedOutput schema / properties / result
        Removed value: -{
        -  "items": {
        -    "$ref": "#/$defs/ParticipantInfo"
        -  },
        -  "title": "Result",
        -  "type": "array"
        -}
      • addedOutput schema / properties / returned
        Added value: +{
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / total
        Added value: +{
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "result"
        -]New value: +[
        +  "participants",
        +  "returned",
        +  "total",
        +  "truncated",
        +  "domain_id",
        +  "mode_effective"
        +]
      • changedOutput schema / title
        Previous value: -"list_participantsOutput"New value: +"ParticipantListing"
    • Changedlist_topics15 fields changed
      • removedInput schema / title
        Removed value: -"list_topicsArguments"
      • removedOutput schema / $defs / QosProfile
        Removed value: -{
        -  "additionalProperties": false,
        -  "description": "QoS profile of one DDS endpoint (reader or writer).\n\nCovers the policies behind most \"subscriber receives nothing\" cases.\nVendor-specific extensions are ignored.",
        -  "properties": {
        -    "data_representation": {
        -      "anyOf": [
        -        {
        -          "items": {
        -            "type": "string"
        -          },
        -          "type": "array"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Data representations the endpoint accepts or offers, from `XCDR1` and `XCDR2`. `None` when not announced.",
        -      "title": "Data Representation"
        -    },
        -    "deadline_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Deadline QoS in nanoseconds. `None` means no deadline (infinite, the DDS default). A reader deadline tighter (smaller) than a writer deadline is incompatible: the writer cannot guarantee the reader's promise.",
        -      "title": "Deadline Ns"
        -    },
        -    "destination_order": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "BY_RECEPTION_TIMESTAMP",
        -            "BY_SOURCE_TIMESTAMP"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "DestinationOrder QoS. `None` when not announced.",
        -      "title": "Destination Order"
        -    },
        -    "durability": {
        -      "description": "DDS Durability QoS. `VOLATILE` writers do not retain samples for late joiners; `TRANSIENT_LOCAL` writers do. A `TRANSIENT_LOCAL` reader cannot match a `VOLATILE` writer.",
        -      "enum": [
        -        "VOLATILE",
        -        "TRANSIENT_LOCAL",
        -        "TRANSIENT",
        -        "PERSISTENT"
        -      ],
        -      "title": "Durability",
        -      "type": "string"
        -    },
        -    "history": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "KEEP_LAST",
        -            "KEEP_ALL"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "DDS History QoS. `KEEP_LAST` keeps a bounded ring buffer of size `history_depth`; `KEEP_ALL` keeps every sample (memory permitting). Mixed `KEEP_ALL` reader with `KEEP_LAST` writer is risky but not strictly incompatible. `None` when it is not known: DDS discovery does not carry History, so it is reported only for TopicForge's own endpoints and for Cyclone DDS peers that set something other than the default (see `history_note`).",
        -      "title": "History"
        -    },
        -    "history_depth": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Depth for `KEEP_LAST`. `None` for `KEEP_ALL` or when `history` is `None`.",
        -      "title": "History Depth"
        -    },
        -    "history_note": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Set when `history` was left out because discovery did not announce it.",
        -      "title": "History Note"
        -    },
        -    "latency_budget_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "LatencyBudget QoS in nanoseconds (a delivery-delay hint). `0` is the DDS default. `None` means infinite or not announced.",
        -      "title": "Latency Budget Ns"
        -    },
        -    "liveliness_kind": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "AUTOMATIC",
        -            "MANUAL_BY_PARTICIPANT",
        -            "MANUAL_BY_TOPIC"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Liveliness QoS kind. `AUTOMATIC` is asserted by the middleware; the two `MANUAL_*` kinds need the application to write or assert liveliness, and the endpoint is declared not alive when it does not within `liveliness_lease_ns`. `None` when not announced.",
        -      "title": "Liveliness Kind"
        -    },
        -    "liveliness_lease_ns": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Liveliness lease duration in nanoseconds. `None` means infinite (the DDS default) or not announced.",
        -      "title": "Liveliness Lease Ns"
        -    },
        -    "ownership_kind": {
        -      "anyOf": [
        -        {
        -          "enum": [
        -            "SHARED",
        -            "EXCLUSIVE"
        -          ],
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Ownership QoS kind. With `EXCLUSIVE`, only the writer with the highest `ownership_strength` delivers samples to a reader. `None` when not announced.",
        -      "title": "Ownership Kind"
        -    },
        -    "ownership_strength": {
        -      "anyOf": [
        -        {
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Ownership strength of a writer (`EXCLUSIVE` ownership). `None` when not announced.",
        -      "title": "Ownership Strength"
        -    },
        -    "partitions": {
        -      "anyOf": [
        -        {
        -          "items": {
        -            "type": "string"
        -          },
        -          "type": "array"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Partition names the endpoint's Publisher or Subscriber belongs to. No Partition policy is the default partition, reported as `[\"\"]` (never an empty list). Endpoints only match when their partitions intersect. `None` only when the backend cannot read partitions at all (unknown, not the default).",
        -      "title": "Partitions"
        -    },
        -    "reliability": {
        -      "description": "DDS Reliability QoS. `RELIABLE` retries lost samples; `BEST_EFFORT` does not. A `RELIABLE` reader cannot match a `BEST_EFFORT` writer.",
        -      "enum": [
        -        "RELIABLE",
        -        "BEST_EFFORT"
        -      ],
        -      "title": "Reliability",
        -      "type": "string"
        -    },
        -    "unknown_policies": {
        -      "anyOf": [
        -        {
        -          "items": {
        -            "type": "string"
        -          },
        -          "type": "array"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Policies the endpoint announced but whose value could not be read (for example `Deadline`). Their duration fields are `None` here, which does NOT mean infinite for these: the scan does not compare them.",
        -      "title": "Unknown Policies"
        -    }
        -  },
        -  "required": [
        -    "reliability",
        -    "durability"
        -  ],
        -  "title": "QosProfile",
        -  "type": "object"
        -}
      • removedOutput schema / $defs / TopicInfo
        Removed value: -{
        -  "additionalProperties": false,
        -  "description": "Description of a single ROS2 topic.\n\nCarries optional DDS-side enrichment fields when the active middleware\nbackend can resolve them (CycloneDDS / Fast DDS). The ROS2 CLI adapter and\nthe mock ROS2 path leave them `None`.",
        -  "properties": {
        -    "message_type": {
        -      "description": "ROS2 message type, e.g. `geometry_msgs/msg/Twist`.",
        -      "title": "Message Type",
        -      "type": "string"
        -    },
        -    "mode_effective": {
        -      "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.",
        -      "enum": [
        -        "mock",
        -        "live"
        -      ],
        -      "title": "Mode Effective",
        -      "type": "string"
        -    },
        -    "name": {
        -      "description": "Fully qualified topic name, e.g. `/cmd_vel`.",
        -      "title": "Name",
        -      "type": "string"
        -    },
        -    "publisher_count": {
        -      "description": "Publishers known to the graph.",
        -      "minimum": 0,
        -      "title": "Publisher Count",
        -      "type": "integer"
        -    },
        -    "qos_durability": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Durability announced by the topic's publishers: `volatile`, `transient_local` (late subscribers receive the last samples; typical of latched topics such as `/tf_static`), or `mixed` when publishers disagree. `null` when unknown, with the same rules as `qos_reliability`.",
        -      "title": "Qos Durability"
        -    },
        -    "qos_profile": {
        -      "anyOf": [
        -        {
        -          "$ref": "#/$defs/QosProfile"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Effective DDS QoS profile for this topic when resolvable. `None` from the ROS2 CLI adapter or when the DDS module is inactive. The DDS module populates this on a best-effort basis (picks one representative endpoint if reader/writer QoS differ)."
        -    },
        -    "qos_reliability": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "Reliability announced by the topic's publishers: `reliable`, `best_effort`, or `mixed` when publishers disagree. `null` when unknown: the topic has no publisher, or the value was not read (`list_topics` does not read QoS; `get_topic_info` does).",
        -      "title": "Qos Reliability"
        -    },
        -    "reader_count": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "DDS reader-endpoint count when the active backend can resolve endpoint-level info (Cyclone / Fast DDS). `None` from the ROS2 CLI adapter or when the DDS module is inactive.",
        -      "title": "Reader Count"
        -    },
        -    "subscriber_count": {
        -      "description": "Subscribers known to the graph.",
        -      "minimum": 0,
        -      "title": "Subscriber Count",
        -      "type": "integer"
        -    },
        -    "writer_count": {
        -      "anyOf": [
        -        {
        -          "minimum": 0,
        -          "type": "integer"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "description": "DDS writer-endpoint count when the active backend can resolve endpoint-level info. `None` from the ROS2 CLI adapter or when the DDS module is inactive.",
        -      "title": "Writer Count"
        -    }
        -  },
        -  "required": [
        -    "name",
        -    "message_type",
        -    "publisher_count",
        -    "subscriber_count",
        -    "mode_effective"
        -  ],
        -  "title": "TopicInfo",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / TopicListItem
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "message_type": {
        +      "type": "string"
        +    },
        +    "name": {
        +      "type": "string"
        +    },
        +    "publisher_count": {
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "subscriber_count": {
        +      "minimum": 0,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "name",
        +    "message_type",
        +    "publisher_count",
        +    "subscriber_count"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / additionalProperties
        Added value: +false
      • addedOutput schema / description
        Added value: +"Envelope returned by `list_topics`."
      • addedOutput schema / properties / mode_effective
        Added value: +{
        +  "description": "`live` or `mock` (fixtures).",
        +  "enum": [
        +    "mock",
        +    "live"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One sentence of context, or `None`."
        +}
      • removedOutput schema / properties / result
        Removed value: -{
        -  "items": {
        -    "$ref": "#/$defs/TopicInfo"
        -  },
        -  "title": "Result",
        -  "type": "array"
        -}
      • addedOutput schema / properties / returned
        Added value: +{
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / topics
        Added value: +{
        +  "items": {
        +    "$ref": "#/$defs/TopicListItem"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / total
        Added value: +{
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "result"
        -]New value: +[
        +  "topics",
        +  "returned",
        +  "total",
        +  "truncated",
        +  "mode_effective"
        +]
      • changedOutput schema / title
        Previous value: -"list_topicsOutput"New value: +"TopicListing"
    • Changedparticipant_events43 fields changed
      • changedInput schema / properties / domain_id / description
        Previous value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value."
      • removedInput schema / properties / domain_id / title
        Removed value: -"Domain Id"
      • addedInput schema / properties / lookback_s
        Added value: +{
        +  "default": 300,
        +  "description": "Window in seconds, 1..86400, default 300. A larger window may hit the 200-event cap.",
        +  "maximum": 86400,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • removedInput schema / properties / lookback_seconds
        Removed value: -{
        -  "default": 300,
        -  "description": "Window (in seconds) over which to return events. Defaults to 300 (5 minutes). Range: 1..86400 (1 second to 24 hours). Larger windows may hit the 200-event cap: narrow the window or filter on `domain_id` when that happens.",
        -  "maximum": 86400,
        -  "minimum": 1,
        -  "title": "Lookback Seconds",
        -  "type": "integer"
        -}
      • removedInput schema / title
        Removed value: -"participant_eventsArguments"
      • addedOutput schema / $defs / NameLifecycle
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "departures": {
        +      "description": "Times a participant with this name was lost.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "lives": {
        +      "description": "Distinct GUIDs that carried this name; restarts = `lives` - 1.",
        +      "minimum": 1,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "lives",
        +    "departures"
        +  ],
        +  "type": "object"
        +}
      • removedOutput schema / $defs / ParticipantEvent / description
        Removed value: -"A point-in-time lifecycle event (discovered or lost) for a DDS participant.\n\n`ParticipantInfo` is the current state; this is one event in its history.\nReturned by `participant_events`."
      • addedOutput schema / $defs / ParticipantEvent / properties / age_s
        Added value: +{
        +  "anyOf": [
        +    {
        +      "minimum": 0,
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Seconds between `timestamp_ns` and this response; `None` in mock mode."
        +}
      • removedOutput schema / $defs / ParticipantEvent / properties / domain_id / description
        Removed value: -"DDS domain id the event occurred on."
      • removedOutput schema / $defs / ParticipantEvent / properties / domain_id / title
        Removed value: -"Domain Id"
      • changedOutput schema / $defs / ParticipantEvent / properties / event_type / description
        Previous value: -"`discovered` when the participant first joined the bus (or rejoined after leaving). `lost` when TopicForge observed a `REMOVED` callback (Fast DDS) or a polling delta where the GUID no longer appears in the DCPSParticipant builtin reader snapshot (Cyclone). The transition is reported once per state change."New value: +"`discovered` (joined or rejoined) or `lost` (removed, or gone from discovery); reported once per change."
      • removedOutput schema / $defs / ParticipantEvent / properties / event_type / title
        Removed value: -"Event Type"
      • removedOutput schema / $defs / ParticipantEvent / properties / guid / description
        Removed value: -"DDS Global Unique Identifier of the participant involved."
      • removedOutput schema / $defs / ParticipantEvent / properties / guid / title
        Removed value: -"Guid"
      • removedOutput schema / $defs / ParticipantEvent / properties / hostname / description
        Removed value: -"Hostname announced by the participant when known, else `None`."
      • removedOutput schema / $defs / ParticipantEvent / properties / hostname / title
        Removed value: -"Hostname"
      • removedOutput schema / $defs / ParticipantEvent / properties / mode_effective
        Removed value: -{
        -  "description": "Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`.",
        -  "enum": [
        -    "mock",
        -    "live"
        -  ],
        -  "title": "Mode Effective",
        -  "type": "string"
        -}
      • removedOutput schema / $defs / ParticipantEvent / properties / name / description
        Removed value: -"Participant name (EntityName QoS) when known, else `None`."
      • removedOutput schema / $defs / ParticipantEvent / properties / name / title
        Removed value: -"Name"
      • changedOutput schema / $defs / ParticipantEvent / properties / observed_ns / description
        Previous value: -"Local wall-clock time (ns since epoch) at which TopicForge noticed the event. Always at or after `timestamp_ns` when the latter is DDS-derived. `None` when not tracked."New value: +"Local ns when TopicForge noticed the event, never before a DDS-derived `timestamp_ns`; `None` when untracked."
      • removedOutput schema / $defs / ParticipantEvent / properties / observed_ns / title
        Removed value: -"Observed Ns"
      • changedOutput schema / $defs / ParticipantEvent / properties / time_source / description
        Previous value: -"Where `timestamp_ns` comes from. `dds_source_timestamp`: the DDS timestamp of the discovery announcement or dispose. For a `lost` event it is an upper bound: exact after a clean shutdown, the lease expiry after a crash (the process died up to one lease earlier: 10 s Cyclone default, 20 s Fast, 100 s RTI), and the two cannot be told apart. `observed_local`: the moment TopicForge noticed (weakest)."New value: +"`dds_source_timestamp` (DDS timestamp of the announcement or dispose; for `lost` an upper bound of the death) or `observed_local` (when TopicForge noticed, weakest)."
      • removedOutput schema / $defs / ParticipantEvent / properties / time_source / title
        Removed value: -"Time Source"
      • changedOutput schema / $defs / ParticipantEvent / properties / timestamp_ns / description
        Previous value: -"Wall-clock timestamp (nanoseconds since epoch) when TopicForge captured the event. For Fast DDS this is when the listener callback ran; for Cyclone this is when the polling delta was computed; for mock fixtures this is a deterministic anchor."New value: +"Wall-clock ns of the event; its origin is `time_source`."
      • removedOutput schema / $defs / ParticipantEvent / properties / timestamp_ns / title
        Removed value: -"Timestamp Ns"
      • removedOutput schema / $defs / ParticipantEvent / properties / vendor / description
        Removed value: -"DDS implementation tag for the participant involved, decoded the same way as `ParticipantInfo.vendor`."
      • removedOutput schema / $defs / ParticipantEvent / properties / vendor / title
        Removed value: -"Vendor"
      • changedOutput schema / $defs / ParticipantEvent / required
        Previous value: -[
        -  "guid",
        -  "event_type",
        -  "vendor",
        -  "timestamp_ns",
        -  "domain_id",
        -  "mode_effective"
        -]New value: +[
        +  "guid",
        +  "event_type",
        +  "vendor",
        +  "timestamp_ns",
        +  "domain_id"
        +]
      • removedOutput schema / $defs / ParticipantEvent / title
        Removed value: -"ParticipantEvent"
      • addedOutput schema / additionalProperties
        Added value: +false
      • addedOutput schema / description
        Added value: +"Envelope returned by `participant_events`."
      • addedOutput schema / properties / domain_id
        Added value: +{
        +  "description": "DDS domain observed (joined at startup).",
        +  "maximum": 232,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / events
        Added value: +{
        +  "items": {
        +    "$ref": "#/$defs/ParticipantEvent"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / mode_effective
        Added value: +{
        +  "description": "`live` or `mock` (fixtures).",
        +  "enum": [
        +    "mock",
        +    "live"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One sentence of context, or `None`."
        +}
      • addedOutput schema / properties / restarts_by_name
        Added value: +{
        +  "additionalProperties": {
        +    "$ref": "#/$defs/NameLifecycle"
        +  },
        +  "description": "Names seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name. Window only.",
        +  "type": "object"
        +}
      • addedOutput schema / properties / restarts_note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set when unnamed participants were left out of `restarts_by_name`."
        +}
      • removedOutput schema / properties / result
        Removed value: -{
        -  "items": {
        -    "$ref": "#/$defs/ParticipantEvent"
        -  },
        -  "title": "Result",
        -  "type": "array"
        -}
      • addedOutput schema / properties / returned
        Added value: +{
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / total
        Added value: +{
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / truncated
        Added value: +{
        +  "description": "True when cut to the 200-event cap.",
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "result"
        -]New value: +[
        +  "events",
        +  "returned",
        +  "total",
        +  "truncated",
        +  "domain_id",
        +  "mode_effective"
        +]
      • changedOutput schema / title
        Previous value: -"participant_eventsOutput"New value: +"ParticipantEventListing"
    • Changedpeek_bag_samples51 fields changed
      • changedInput schema / properties / count / description
        Previous value: -"Maximum number of recent messages to return. Defaults to 5; silently clamped to 50 (the hard cap that keeps tool output bounded; read it from `health_check.max_sample_count`). Negative values raise an error. The returned `SampleResult.count` reflects the actual number of samples produced: it can be lower than the request (empty topic, timeout, mock fixture shorter than requested)."New value: +"Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower."
      • removedInput schema / properties / count / title
        Removed value: -"Count"
      • changedInput schema / properties / path / description
        Previous value: -"Path to a bag: a file ending in `.mcap` or `.db3`, or a `rosbag2_*` directory. `peek_bag_samples` also reads ROS 1 `.bag` files; `analyze_bag` does not (`ros2 bag info` cannot open them). Leading/trailing whitespace is stripped. Null bytes and otherwise malformed filesystem paths are rejected. Existence and bag format are validated by the live adapter (mock mode accepts any well-formed path)."New value: +"Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed path."
      • removedInput schema / properties / path / title
        Removed value: -"Path"
      • changedInput schema / properties / topic / description
        Previous value: -"Fully qualified ROS2 topic name starting with `/`, e.g. `/cmd_vel` or `/camera/image_raw`. Each `/`-separated segment must start with a letter or underscore and contain only letters, digits, and underscores; everything else (whitespace, quotes, shell metacharacters, `//`, trailing `/`) is rejected before reaching the `ros2` CLI."New value: +"Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI."
      • removedInput schema / properties / topic / title
        Removed value: -"Topic"
      • removedInput schema / title
        Removed value: -"peek_bag_samplesArguments"
      • addedOutput schema / $defs / ImageSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "data_length": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixel buffer size in bytes (see `data_length_basis`)."
        +    },
        +    "data_length_basis": {
        +      "anyOf": [
        +        {
        +          "enum": [
        +            "payload",
        +            "step_x_height"
        +          ],
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)."
        +    },
        +    "encoding": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixel encoding, e.g. `rgb8`."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the camera."
        +    },
        +    "height": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixels."
        +    },
        +    "is_bigendian": {
        +      "anyOf": [
        +        {
        +          "type": "boolean"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Big-endian pixels."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when partial; else `null`."
        +    },
        +    "step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Row length, bytes."
        +    },
        +    "summary_type": {
        +      "const": "image",
        +      "type": "string"
        +    },
        +    "width": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixels."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "width",
        +    "height",
        +    "encoding",
        +    "step",
        +    "is_bigendian",
        +    "data_length",
        +    "data_length_basis"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ImuSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angular_velocity_norm": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `angular_velocity`, rad/s."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the IMU."
        +    },
        +    "linear_acceleration_norm": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `linear_acceleration`, m/s^2."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when a value is unusable; else `null`."
        +    },
        +    "orientation_valid": {
        +      "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.",
        +      "type": "boolean"
        +    },
        +    "pitch": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about y, radians."
        +    },
        +    "roll": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about x, radians."
        +    },
        +    "summary_type": {
        +      "const": "imu",
        +      "type": "string"
        +    },
        +    "yaw": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about z, radians."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "orientation_valid",
        +    "roll",
        +    "pitch",
        +    "yaw",
        +    "angular_velocity_norm",
        +    "linear_acceleration_norm"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / LaserScanSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angle_increment": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Angle between beams, rad."
        +    },
        +    "angle_max": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bearing of the last beam, rad."
        +    },
        +    "angle_min": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bearing of beam 0, rad."
        +    },
        +    "beam_count": {
        +      "description": "Beams in `ranges`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "closest_obstacle": {
        +      "anyOf": [
        +        {
        +          "$ref": "#/$defs/ScanReturn"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Closest valid return of the scan; `null` with `note`."
        +    },
        +    "finite_count": {
        +      "description": "Beams with a finite range.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame."
        +    },
        +    "inf_count": {
        +      "description": "Beams at `+inf`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "nan_count": {
        +      "description": "Beams at `nan`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "neg_inf_count": {
        +      "description": "Beams at `-inf`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when the summary is partial; else `null`."
        +    },
        +    "out_of_range_count": {
        +      "description": "Finite ranges outside `range_min`..`range_max` (not valid).",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "range_max": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Largest valid range, m."
        +    },
        +    "range_min": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Smallest valid range, m."
        +    },
        +    "sectors": {
        +      "$ref": "#/$defs/ScanSectors",
        +      "description": "Closest valid return per sector."
        +    },
        +    "summary_type": {
        +      "const": "laser_scan",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "beam_count",
        +    "angle_min",
        +    "angle_max",
        +    "angle_increment",
        +    "range_min",
        +    "range_max",
        +    "finite_count",
        +    "inf_count",
        +    "neg_inf_count",
        +    "nan_count",
        +    "out_of_range_count",
        +    "closest_obstacle",
        +    "sectors"
        +  ],
        +  "type": "object"
        +}
      • removedOutput schema / $defs / MessageSample / description
        Removed value: -"A single sampled message on a topic."
      • removedOutput schema / $defs / MessageSample / properties / message_type / description
        Removed value: -"ROS2 message type of this sample, e.g. `geometry_msgs/msg/Twist`."
      • removedOutput schema / $defs / MessageSample / properties / message_type / title
        Removed value: -"Message Type"
      • changedOutput schema / $defs / MessageSample / properties / payload / description
        Previous value: -"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."New value: +"Message fields as nested values; `nan`/`inf` are strings. Live `sample_messages` adds `_truncated_fields` (cut paths) and `_raw_text` (unparsable output)."
      • removedOutput schema / $defs / MessageSample / properties / payload / title
        Removed value: -"Payload"
      • changedOutput schema / $defs / MessageSample / properties / received_ns / description
        Previous value: -"Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise."New value: +"Wall-clock ns when the `ros2` CLI printed the message (live `sample_messages` only)."
      • removedOutput schema / $defs / MessageSample / properties / received_ns / title
        Removed value: -"Received Ns"
      • changedOutput schema / $defs / MessageSample / properties / recorded_ns / description
        Previous value: -"Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role."New value: +"Bag record time in ns (`peek_bag_samples` only)."
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / title
        Removed value: -"Recorded Ns"
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "header",
        -      "none",
        -      "recorded"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / default
        Removed value: -null
      • changedOutput schema / $defs / MessageSample / properties / stamp_source / description
        Previous value: -"Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say."New value: +"Origin of `timestamp_ns`: `header`, `payload`, `recorded` (bag record time), `dds_source` or `none` (0)."
      • addedOutput schema / $defs / MessageSample / properties / stamp_source / enum
        Added value: +[
        +  "header",
        +  "payload",
        +  "recorded",
        +  "dds_source",
        +  "none"
        +]
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / title
        Removed value: -"Stamp Source"
      • addedOutput schema / $defs / MessageSample / properties / stamp_source / type
        Added value: +"string"
      • addedOutput schema / $defs / MessageSample / properties / summary
        Added value: +{
        +  "anyOf": [
        +    {
        +      "discriminator": {
        +        "mapping": {
        +          "image": "#/$defs/ImageSummary",
        +          "imu": "#/$defs/ImuSummary",
        +          "laser_scan": "#/$defs/LaserScanSummary",
        +          "odometry": "#/$defs/OdometrySummary",
        +          "point_cloud2": "#/$defs/PointCloud2Summary"
        +        },
        +        "propertyName": "summary_type"
        +      },
        +      "oneOf": [
        +        {
        +          "$ref": "#/$defs/LaserScanSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/OdometrySummary"
        +        },
        +        {
        +          "$ref": "#/$defs/ImuSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/ImageSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/PointCloud2Summary"
        +        }
        +      ]
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Condensed reading of the whole message (see `summary_type`); `null` for other types and DDS peeks."
        +}
      • changedOutput schema / $defs / MessageSample / properties / timestamp_ns / description
        Previous value: -"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."New value: +"Publisher-clock ns: `header.stamp`, else the time in the body of `Clock`, `TFMessage` or `Log`, else 0 (see `stamp_source`); not the arrival time."
      • removedOutput schema / $defs / MessageSample / properties / timestamp_ns / title
        Removed value: -"Timestamp Ns"
      • removedOutput schema / $defs / MessageSample / properties / topic / description
        Removed value: -"Fully qualified topic name the sample was taken from, e.g. `/cmd_vel`."
      • removedOutput schema / $defs / MessageSample / properties / topic / title
        Removed value: -"Topic"
      • changedOutput schema / $defs / MessageSample / required
        Previous value: -[
        -  "topic",
        -  "message_type",
        -  "timestamp_ns"
        -]New value: +[
        +  "topic",
        +  "message_type",
        +  "timestamp_ns",
        +  "stamp_source"
        +]
      • removedOutput schema / $defs / MessageSample / title
        Removed value: -"MessageSample"
      • addedOutput schema / $defs / OdometrySummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angular_z": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`twist.twist.angular.z`, rad/s."
        +    },
        +    "child_frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`child_frame_id` (frame of the twist)."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` (frame of the pose)."
        +    },
        +    "linear_speed": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `twist.twist.linear`, m/s."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when a value is missing; else `null`."
        +    },
        +    "position_x": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.x`, m."
        +    },
        +    "position_y": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.y`, m."
        +    },
        +    "position_z": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.z`, m."
        +    },
        +    "summary_type": {
        +      "const": "odometry",
        +      "type": "string"
        +    },
        +    "yaw": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Heading from the pose quaternion, radians."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "child_frame_id",
        +    "linear_speed",
        +    "angular_z",
        +    "position_x",
        +    "position_y",
        +    "position_z",
        +    "yaw"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / PointCloud2Summary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "fields": {
        +      "description": "Point layout.",
        +      "items": {
        +        "$ref": "#/$defs/PointFieldSummary"
        +      },
        +      "type": "array"
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the cloud."
        +    },
        +    "height": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rows (1: unorganized)."
        +    },
        +    "is_dense": {
        +      "anyOf": [
        +        {
        +          "type": "boolean"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "True when no point is invalid."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when partial; else `null`."
        +    },
        +    "point_count": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`width` times `height`."
        +    },
        +    "point_step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bytes per point."
        +    },
        +    "row_step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bytes per row."
        +    },
        +    "summary_type": {
        +      "const": "point_cloud2",
        +      "type": "string"
        +    },
        +    "width": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Points per row."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "width",
        +    "height",
        +    "point_count",
        +    "point_step",
        +    "row_step",
        +    "is_dense",
        +    "fields"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / PointFieldSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "count": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Elements per point."
        +    },
        +    "datatype": {
        +      "description": "Element type, e.g. `FLOAT32`.",
        +      "type": "string"
        +    },
        +    "name": {
        +      "description": "Field name, e.g. `x`.",
        +      "type": "string"
        +    },
        +    "offset": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Byte offset in a point."
        +    }
        +  },
        +  "required": [
        +    "name",
        +    "datatype",
        +    "offset",
        +    "count"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanReturn
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "beam_index": {
        +      "description": "Index in `ranges`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "bearing": {
        +      "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.",
        +      "type": "number"
        +    },
        +    "range": {
        +      "description": "Distance, meters.",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "range",
        +    "bearing",
        +    "beam_index"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanSector
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "beam_count": {
        +      "description": "Beams inside the sector (0: not covered).",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "closest": {
        +      "anyOf": [
        +        {
        +          "$ref": "#/$defs/ScanReturn"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Closest valid return in the sector; `null` with `note`."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Why `closest` is `null`; `null` when it is set."
        +    }
        +  },
        +  "required": [
        +    "beam_count",
        +    "closest"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanSectors
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "front": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings within +-45 degrees of +x."
        +    },
        +    "left": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings from 45 to 135 degrees."
        +    },
        +    "rear": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings beyond +-135 degrees."
        +    },
        +    "right": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings from -135 to -45 degrees."
        +    }
        +  },
        +  "required": [
        +    "front",
        +    "left",
        +    "right",
        +    "rear"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / TopicRate
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "basis": {
        +      "description": "Clock the rate is measured on: `received_ns` (live) or `recorded_ns` (bag).",
        +      "enum": [
        +        "received_ns",
        +        "recorded_ns"
        +      ],
        +      "type": "string"
        +    },
        +    "interval_cv": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Interval standard deviation over mean; `null` under 2 messages."
        +    },
        +    "interval_median_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Median interval; `null` under 2 messages."
        +    },
        +    "max_gap_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Longest interval; `null` under 2 messages."
        +    },
        +    "mean_interval_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Mean interval; `null` under 2 messages."
        +    },
        +    "message_count": {
        +      "description": "Messages the rate is computed from.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "observed_frequency_hz": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Messages per second on `basis`, drain excluded; the rate the caller received. `null` under 2 messages."
        +    },
        +    "sim_frequency_hz": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Same rate on the messages' own stamps (publisher clock); `null` without advancing stamps."
        +    },
        +    "startup_burst_count": {
        +      "description": "Leading messages set aside as a start-up drain (live only); still in `message_count`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "trailing_gap_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Time from the last message to the end of a live observation; else `null`."
        +    },
        +    "verdict": {
        +      "description": "First match: `silent` (none), `insufficient` (under 5), `intermittent` (an interval over 3x the median), `stable` (cv < 0.2), `jittery` (< 0.5), `erratic`. Describes this window, not a diagnosis.",
        +      "enum": [
        +        "silent",
        +        "insufficient",
        +        "intermittent",
        +        "stable",
        +        "jittery",
        +        "erratic"
        +      ],
        +      "type": "string"
        +    },
        +    "verdict_note": {
        +      "description": "The verdict in plain words.",
        +      "type": "string"
        +    },
        +    "window_s": {
        +      "description": "Observation length, seconds.",
        +      "minimum": 0,
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "basis",
        +    "message_count",
        +    "startup_burst_count",
        +    "window_s",
        +    "mean_interval_s",
        +    "interval_median_s",
        +    "max_gap_s",
        +    "interval_cv",
        +    "observed_frequency_hz",
        +    "sim_frequency_hz",
        +    "trailing_gap_s",
        +    "verdict",
        +    "verdict_note"
        +  ],
        +  "type": "object"
        +}
      • changedOutput schema / description
        Previous value: -"Envelope returned by the `sample_messages` tool."New value: +"Samples returned by `sample_messages`, `peek_dds_samples` and `peek_bag_samples`."
      • changedOutput schema / properties / count / description
        Previous value: -"Number of samples actually returned. May be 0 (no publisher active in live mode, or empty mock fixture), less than the requested count (topic yielded fewer messages within the timeout), or capped by the the silent maximum of 50: request `count > 50` and you will receive at most 50 without warning."New value: +"Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50)."
      • removedOutput schema / properties / count / title
        Removed value: -"Count"
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • changedOutput schema / properties / note / description
        Previous value: -"Why `samples` is empty or limited, when the cause is not obvious (for example payload decoding is disabled for DDS user topics). `None` when there is nothing to add."New value: +"Why `samples` is empty or limited, when not obvious; else `None`."
      • removedOutput schema / properties / note / title
        Removed value: -"Note"
      • addedOutput schema / properties / rate
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/TopicRate"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10."
        +}
      • changedOutput schema / properties / samples / description
        Previous value: -"The sampled messages, ordered as received from the backend."New value: +"The samples, in backend order."
      • removedOutput schema / properties / samples / title
        Removed value: -"Samples"
      • removedOutput schema / properties / topic / description
        Removed value: -"Topic the samples were taken from, echoed from the request."
      • removedOutput schema / properties / topic / title
        Removed value: -"Topic"
    • Changedpeek_dds_samples49 fields changed
      • changedInput schema / properties / count / description
        Previous value: -"Maximum number of recent messages to return. Defaults to 5; silently clamped to 50 (the hard cap that keeps tool output bounded; read it from `health_check.max_sample_count`). Negative values raise an error. The returned `SampleResult.count` reflects the actual number of samples produced: it can be lower than the request (empty topic, timeout, mock fixture shorter than requested)."New value: +"Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower."
      • removedInput schema / properties / count / title
        Removed value: -"Count"
      • changedInput schema / properties / topic / description
        Previous value: -"DDS topic name. Bare DDS names such as `scan` are valid, as are ROS 2 mangled names such as `rt/scan` and the builtin discovery topics `DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`. Letters, digits, `_`, `/` and `::` are allowed; anything else is rejected."New value: +"DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only."
      • removedInput schema / properties / topic / title
        Removed value: -"Topic"
      • removedInput schema / title
        Removed value: -"peek_dds_samplesArguments"
      • addedOutput schema / $defs / ImageSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "data_length": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixel buffer size in bytes (see `data_length_basis`)."
        +    },
        +    "data_length_basis": {
        +      "anyOf": [
        +        {
        +          "enum": [
        +            "payload",
        +            "step_x_height"
        +          ],
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)."
        +    },
        +    "encoding": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixel encoding, e.g. `rgb8`."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the camera."
        +    },
        +    "height": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixels."
        +    },
        +    "is_bigendian": {
        +      "anyOf": [
        +        {
        +          "type": "boolean"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Big-endian pixels."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when partial; else `null`."
        +    },
        +    "step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Row length, bytes."
        +    },
        +    "summary_type": {
        +      "const": "image",
        +      "type": "string"
        +    },
        +    "width": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixels."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "width",
        +    "height",
        +    "encoding",
        +    "step",
        +    "is_bigendian",
        +    "data_length",
        +    "data_length_basis"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ImuSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angular_velocity_norm": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `angular_velocity`, rad/s."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the IMU."
        +    },
        +    "linear_acceleration_norm": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `linear_acceleration`, m/s^2."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when a value is unusable; else `null`."
        +    },
        +    "orientation_valid": {
        +      "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.",
        +      "type": "boolean"
        +    },
        +    "pitch": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about y, radians."
        +    },
        +    "roll": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about x, radians."
        +    },
        +    "summary_type": {
        +      "const": "imu",
        +      "type": "string"
        +    },
        +    "yaw": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about z, radians."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "orientation_valid",
        +    "roll",
        +    "pitch",
        +    "yaw",
        +    "angular_velocity_norm",
        +    "linear_acceleration_norm"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / LaserScanSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angle_increment": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Angle between beams, rad."
        +    },
        +    "angle_max": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bearing of the last beam, rad."
        +    },
        +    "angle_min": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bearing of beam 0, rad."
        +    },
        +    "beam_count": {
        +      "description": "Beams in `ranges`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "closest_obstacle": {
        +      "anyOf": [
        +        {
        +          "$ref": "#/$defs/ScanReturn"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Closest valid return of the scan; `null` with `note`."
        +    },
        +    "finite_count": {
        +      "description": "Beams with a finite range.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame."
        +    },
        +    "inf_count": {
        +      "description": "Beams at `+inf`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "nan_count": {
        +      "description": "Beams at `nan`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "neg_inf_count": {
        +      "description": "Beams at `-inf`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when the summary is partial; else `null`."
        +    },
        +    "out_of_range_count": {
        +      "description": "Finite ranges outside `range_min`..`range_max` (not valid).",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "range_max": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Largest valid range, m."
        +    },
        +    "range_min": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Smallest valid range, m."
        +    },
        +    "sectors": {
        +      "$ref": "#/$defs/ScanSectors",
        +      "description": "Closest valid return per sector."
        +    },
        +    "summary_type": {
        +      "const": "laser_scan",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "beam_count",
        +    "angle_min",
        +    "angle_max",
        +    "angle_increment",
        +    "range_min",
        +    "range_max",
        +    "finite_count",
        +    "inf_count",
        +    "neg_inf_count",
        +    "nan_count",
        +    "out_of_range_count",
        +    "closest_obstacle",
        +    "sectors"
        +  ],
        +  "type": "object"
        +}
      • removedOutput schema / $defs / MessageSample / description
        Removed value: -"A single sampled message on a topic."
      • removedOutput schema / $defs / MessageSample / properties / message_type / description
        Removed value: -"ROS2 message type of this sample, e.g. `geometry_msgs/msg/Twist`."
      • removedOutput schema / $defs / MessageSample / properties / message_type / title
        Removed value: -"Message Type"
      • changedOutput schema / $defs / MessageSample / properties / payload / description
        Previous value: -"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."New value: +"Message fields as nested values; `nan`/`inf` are strings. Live `sample_messages` adds `_truncated_fields` (cut paths) and `_raw_text` (unparsable output)."
      • removedOutput schema / $defs / MessageSample / properties / payload / title
        Removed value: -"Payload"
      • changedOutput schema / $defs / MessageSample / properties / received_ns / description
        Previous value: -"Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise."New value: +"Wall-clock ns when the `ros2` CLI printed the message (live `sample_messages` only)."
      • removedOutput schema / $defs / MessageSample / properties / received_ns / title
        Removed value: -"Received Ns"
      • changedOutput schema / $defs / MessageSample / properties / recorded_ns / description
        Previous value: -"Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role."New value: +"Bag record time in ns (`peek_bag_samples` only)."
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / title
        Removed value: -"Recorded Ns"
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "header",
        -      "none",
        -      "recorded"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / default
        Removed value: -null
      • changedOutput schema / $defs / MessageSample / properties / stamp_source / description
        Previous value: -"Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say."New value: +"Origin of `timestamp_ns`: `header`, `payload`, `recorded` (bag record time), `dds_source` or `none` (0)."
      • addedOutput schema / $defs / MessageSample / properties / stamp_source / enum
        Added value: +[
        +  "header",
        +  "payload",
        +  "recorded",
        +  "dds_source",
        +  "none"
        +]
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / title
        Removed value: -"Stamp Source"
      • addedOutput schema / $defs / MessageSample / properties / stamp_source / type
        Added value: +"string"
      • addedOutput schema / $defs / MessageSample / properties / summary
        Added value: +{
        +  "anyOf": [
        +    {
        +      "discriminator": {
        +        "mapping": {
        +          "image": "#/$defs/ImageSummary",
        +          "imu": "#/$defs/ImuSummary",
        +          "laser_scan": "#/$defs/LaserScanSummary",
        +          "odometry": "#/$defs/OdometrySummary",
        +          "point_cloud2": "#/$defs/PointCloud2Summary"
        +        },
        +        "propertyName": "summary_type"
        +      },
        +      "oneOf": [
        +        {
        +          "$ref": "#/$defs/LaserScanSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/OdometrySummary"
        +        },
        +        {
        +          "$ref": "#/$defs/ImuSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/ImageSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/PointCloud2Summary"
        +        }
        +      ]
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Condensed reading of the whole message (see `summary_type`); `null` for other types and DDS peeks."
        +}
      • changedOutput schema / $defs / MessageSample / properties / timestamp_ns / description
        Previous value: -"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."New value: +"Publisher-clock ns: `header.stamp`, else the time in the body of `Clock`, `TFMessage` or `Log`, else 0 (see `stamp_source`); not the arrival time."
      • removedOutput schema / $defs / MessageSample / properties / timestamp_ns / title
        Removed value: -"Timestamp Ns"
      • removedOutput schema / $defs / MessageSample / properties / topic / description
        Removed value: -"Fully qualified topic name the sample was taken from, e.g. `/cmd_vel`."
      • removedOutput schema / $defs / MessageSample / properties / topic / title
        Removed value: -"Topic"
      • changedOutput schema / $defs / MessageSample / required
        Previous value: -[
        -  "topic",
        -  "message_type",
        -  "timestamp_ns"
        -]New value: +[
        +  "topic",
        +  "message_type",
        +  "timestamp_ns",
        +  "stamp_source"
        +]
      • removedOutput schema / $defs / MessageSample / title
        Removed value: -"MessageSample"
      • addedOutput schema / $defs / OdometrySummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angular_z": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`twist.twist.angular.z`, rad/s."
        +    },
        +    "child_frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`child_frame_id` (frame of the twist)."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` (frame of the pose)."
        +    },
        +    "linear_speed": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `twist.twist.linear`, m/s."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when a value is missing; else `null`."
        +    },
        +    "position_x": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.x`, m."
        +    },
        +    "position_y": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.y`, m."
        +    },
        +    "position_z": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.z`, m."
        +    },
        +    "summary_type": {
        +      "const": "odometry",
        +      "type": "string"
        +    },
        +    "yaw": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Heading from the pose quaternion, radians."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "child_frame_id",
        +    "linear_speed",
        +    "angular_z",
        +    "position_x",
        +    "position_y",
        +    "position_z",
        +    "yaw"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / PointCloud2Summary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "fields": {
        +      "description": "Point layout.",
        +      "items": {
        +        "$ref": "#/$defs/PointFieldSummary"
        +      },
        +      "type": "array"
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the cloud."
        +    },
        +    "height": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rows (1: unorganized)."
        +    },
        +    "is_dense": {
        +      "anyOf": [
        +        {
        +          "type": "boolean"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "True when no point is invalid."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when partial; else `null`."
        +    },
        +    "point_count": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`width` times `height`."
        +    },
        +    "point_step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bytes per point."
        +    },
        +    "row_step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bytes per row."
        +    },
        +    "summary_type": {
        +      "const": "point_cloud2",
        +      "type": "string"
        +    },
        +    "width": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Points per row."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "width",
        +    "height",
        +    "point_count",
        +    "point_step",
        +    "row_step",
        +    "is_dense",
        +    "fields"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / PointFieldSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "count": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Elements per point."
        +    },
        +    "datatype": {
        +      "description": "Element type, e.g. `FLOAT32`.",
        +      "type": "string"
        +    },
        +    "name": {
        +      "description": "Field name, e.g. `x`.",
        +      "type": "string"
        +    },
        +    "offset": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Byte offset in a point."
        +    }
        +  },
        +  "required": [
        +    "name",
        +    "datatype",
        +    "offset",
        +    "count"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanReturn
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "beam_index": {
        +      "description": "Index in `ranges`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "bearing": {
        +      "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.",
        +      "type": "number"
        +    },
        +    "range": {
        +      "description": "Distance, meters.",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "range",
        +    "bearing",
        +    "beam_index"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanSector
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "beam_count": {
        +      "description": "Beams inside the sector (0: not covered).",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "closest": {
        +      "anyOf": [
        +        {
        +          "$ref": "#/$defs/ScanReturn"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Closest valid return in the sector; `null` with `note`."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Why `closest` is `null`; `null` when it is set."
        +    }
        +  },
        +  "required": [
        +    "beam_count",
        +    "closest"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanSectors
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "front": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings within +-45 degrees of +x."
        +    },
        +    "left": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings from 45 to 135 degrees."
        +    },
        +    "rear": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings beyond +-135 degrees."
        +    },
        +    "right": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings from -135 to -45 degrees."
        +    }
        +  },
        +  "required": [
        +    "front",
        +    "left",
        +    "right",
        +    "rear"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / TopicRate
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "basis": {
        +      "description": "Clock the rate is measured on: `received_ns` (live) or `recorded_ns` (bag).",
        +      "enum": [
        +        "received_ns",
        +        "recorded_ns"
        +      ],
        +      "type": "string"
        +    },
        +    "interval_cv": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Interval standard deviation over mean; `null` under 2 messages."
        +    },
        +    "interval_median_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Median interval; `null` under 2 messages."
        +    },
        +    "max_gap_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Longest interval; `null` under 2 messages."
        +    },
        +    "mean_interval_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Mean interval; `null` under 2 messages."
        +    },
        +    "message_count": {
        +      "description": "Messages the rate is computed from.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "observed_frequency_hz": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Messages per second on `basis`, drain excluded; the rate the caller received. `null` under 2 messages."
        +    },
        +    "sim_frequency_hz": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Same rate on the messages' own stamps (publisher clock); `null` without advancing stamps."
        +    },
        +    "startup_burst_count": {
        +      "description": "Leading messages set aside as a start-up drain (live only); still in `message_count`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "trailing_gap_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Time from the last message to the end of a live observation; else `null`."
        +    },
        +    "verdict": {
        +      "description": "First match: `silent` (none), `insufficient` (under 5), `intermittent` (an interval over 3x the median), `stable` (cv < 0.2), `jittery` (< 0.5), `erratic`. Describes this window, not a diagnosis.",
        +      "enum": [
        +        "silent",
        +        "insufficient",
        +        "intermittent",
        +        "stable",
        +        "jittery",
        +        "erratic"
        +      ],
        +      "type": "string"
        +    },
        +    "verdict_note": {
        +      "description": "The verdict in plain words.",
        +      "type": "string"
        +    },
        +    "window_s": {
        +      "description": "Observation length, seconds.",
        +      "minimum": 0,
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "basis",
        +    "message_count",
        +    "startup_burst_count",
        +    "window_s",
        +    "mean_interval_s",
        +    "interval_median_s",
        +    "max_gap_s",
        +    "interval_cv",
        +    "observed_frequency_hz",
        +    "sim_frequency_hz",
        +    "trailing_gap_s",
        +    "verdict",
        +    "verdict_note"
        +  ],
        +  "type": "object"
        +}
      • changedOutput schema / description
        Previous value: -"Envelope returned by the `sample_messages` tool."New value: +"Samples returned by `sample_messages`, `peek_dds_samples` and `peek_bag_samples`."
      • changedOutput schema / properties / count / description
        Previous value: -"Number of samples actually returned. May be 0 (no publisher active in live mode, or empty mock fixture), less than the requested count (topic yielded fewer messages within the timeout), or capped by the the silent maximum of 50: request `count > 50` and you will receive at most 50 without warning."New value: +"Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50)."
      • removedOutput schema / properties / count / title
        Removed value: -"Count"
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • changedOutput schema / properties / note / description
        Previous value: -"Why `samples` is empty or limited, when the cause is not obvious (for example payload decoding is disabled for DDS user topics). `None` when there is nothing to add."New value: +"Why `samples` is empty or limited, when not obvious; else `None`."
      • removedOutput schema / properties / note / title
        Removed value: -"Note"
      • addedOutput schema / properties / rate
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/TopicRate"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10."
        +}
      • changedOutput schema / properties / samples / description
        Previous value: -"The sampled messages, ordered as received from the backend."New value: +"The samples, in backend order."
      • removedOutput schema / properties / samples / title
        Removed value: -"Samples"
      • removedOutput schema / properties / topic / description
        Removed value: -"Topic the samples were taken from, echoed from the request."
      • removedOutput schema / properties / topic / title
        Removed value: -"Topic"
    • Changedsample_messages56 fields changed
      • changedInput schema / properties / arrays_summary_only / description
        Previous value: -"When true, array fields are replaced by a short type and length summary instead of their elements. Use it to inspect the non-array fields of large messages (images, scans, point clouds). Defaults to false."New value: +"When true, arrays become a type-and-length summary: use it to read the non-array fields of large messages. Default false."
      • removedInput schema / properties / arrays_summary_only / title
        Removed value: -"Arrays Summary Only"
      • changedInput schema / properties / count / description
        Previous value: -"Number of messages to wait for. Defaults to 5; at most 50 (a larger request is capped to 50 and `note` says so). 0 returns nothing. `SampleResult.count` is how many arrived: fewer than requested when the topic publishes slowly or the deadline (`timeout_s`) ran out."New value: +"Messages to wait for, default 5, at most 50 (more is capped and `note` says so); 0 returns nothing. Fewer arrive when the topic is slow or `timeout_s` runs out."
      • removedInput schema / properties / count / title
        Removed value: -"Count"
      • changedInput schema / properties / max_array_length / description
        Previous value: -"Longest array, string or bytes value to return in full, 1..65536; longer ones are cut after their first N elements or characters and the field's dotted path is listed in the sample's `_truncated_fields`. Defaults to 128, the `ros2 topic echo` default, which cuts a 541-beam `LaserScan` after 128 ranges. Pass null to return everything in full (large for images and point clouds; a message over the server's size cap, 1 MiB by default, is dropped with a note, and a very large message may not print before the deadline)."New value: +"Longest array, string or bytes value returned in full, 1..65536, default 128 (a 541-beam `LaserScan` is cut to 128 ranges; its `summary` still covers all). Longer values are cut and listed in `_truncated_fields`. Null returns everything (large for images and clouds; a message over 1 MiB is dropped with a note)."
      • removedInput schema / properties / max_array_length / title
        Removed value: -"Max Array Length"
      • changedInput schema / properties / timeout_s / description
        Previous value: -"Seconds to wait for the messages, 1..45, default 10. Bounds the whole call, topic lookup and the `ros2` CLI start-up (a few seconds on a slow machine) included: the call returns within about `timeout_s` + 2 s. Whatever arrived by then is returned with a `note`. Raise it for a topic that publishes slower than 1 Hz. The server handles one call at a time, so a long wait delays other tool calls."New value: +"Seconds to wait, 1..40, default 10, covering topic lookup and CLI start-up. The call returns within `timeout_s` plus a few seconds, with what arrived and a `note`. Raise it for a topic slower than 1 Hz."
      • changedInput schema / properties / timeout_s / maximum
        Previous value: -45New value: +40
      • removedInput schema / properties / timeout_s / title
        Removed value: -"Timeout S"
      • changedInput schema / properties / topic / description
        Previous value: -"Fully qualified ROS2 topic name starting with `/`, e.g. `/cmd_vel` or `/camera/image_raw`. Each `/`-separated segment must start with a letter or underscore and contain only letters, digits, and underscores; everything else (whitespace, quotes, shell metacharacters, `//`, trailing `/`) is rejected before reaching the `ros2` CLI."New value: +"Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI."
      • removedInput schema / properties / topic / title
        Removed value: -"Topic"
      • removedInput schema / title
        Removed value: -"sample_messagesArguments"
      • addedOutput schema / $defs / ImageSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "data_length": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixel buffer size in bytes (see `data_length_basis`)."
        +    },
        +    "data_length_basis": {
        +      "anyOf": [
        +        {
        +          "enum": [
        +            "payload",
        +            "step_x_height"
        +          ],
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`payload` (counted) or `step_x_height` (array cut, so step times height)."
        +    },
        +    "encoding": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixel encoding, e.g. `rgb8`."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the camera."
        +    },
        +    "height": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixels."
        +    },
        +    "is_bigendian": {
        +      "anyOf": [
        +        {
        +          "type": "boolean"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Big-endian pixels."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when partial; else `null`."
        +    },
        +    "step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Row length, bytes."
        +    },
        +    "summary_type": {
        +      "const": "image",
        +      "type": "string"
        +    },
        +    "width": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pixels."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "width",
        +    "height",
        +    "encoding",
        +    "step",
        +    "is_bigendian",
        +    "data_length",
        +    "data_length_basis"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ImuSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angular_velocity_norm": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `angular_velocity`, rad/s."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the IMU."
        +    },
        +    "linear_acceleration_norm": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `linear_acceleration`, m/s^2."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when a value is unusable; else `null`."
        +    },
        +    "orientation_valid": {
        +      "description": "False when the IMU gives no orientation: `roll`, `pitch`, `yaw` are `null`.",
        +      "type": "boolean"
        +    },
        +    "pitch": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about y, radians."
        +    },
        +    "roll": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about x, radians."
        +    },
        +    "summary_type": {
        +      "const": "imu",
        +      "type": "string"
        +    },
        +    "yaw": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rotation about z, radians."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "orientation_valid",
        +    "roll",
        +    "pitch",
        +    "yaw",
        +    "angular_velocity_norm",
        +    "linear_acceleration_norm"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / LaserScanSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angle_increment": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Angle between beams, rad."
        +    },
        +    "angle_max": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bearing of the last beam, rad."
        +    },
        +    "angle_min": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bearing of beam 0, rad."
        +    },
        +    "beam_count": {
        +      "description": "Beams in `ranges`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "closest_obstacle": {
        +      "anyOf": [
        +        {
        +          "$ref": "#/$defs/ScanReturn"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Closest valid return of the scan; `null` with `note`."
        +    },
        +    "finite_count": {
        +      "description": "Beams with a finite range.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id`; every angle is in this sensor frame, not the base frame."
        +    },
        +    "inf_count": {
        +      "description": "Beams at `+inf`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "nan_count": {
        +      "description": "Beams at `nan`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "neg_inf_count": {
        +      "description": "Beams at `-inf`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when the summary is partial; else `null`."
        +    },
        +    "out_of_range_count": {
        +      "description": "Finite ranges outside `range_min`..`range_max` (not valid).",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "range_max": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Largest valid range, m."
        +    },
        +    "range_min": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Smallest valid range, m."
        +    },
        +    "sectors": {
        +      "$ref": "#/$defs/ScanSectors",
        +      "description": "Closest valid return per sector."
        +    },
        +    "summary_type": {
        +      "const": "laser_scan",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "beam_count",
        +    "angle_min",
        +    "angle_max",
        +    "angle_increment",
        +    "range_min",
        +    "range_max",
        +    "finite_count",
        +    "inf_count",
        +    "neg_inf_count",
        +    "nan_count",
        +    "out_of_range_count",
        +    "closest_obstacle",
        +    "sectors"
        +  ],
        +  "type": "object"
        +}
      • removedOutput schema / $defs / MessageSample / description
        Removed value: -"A single sampled message on a topic."
      • removedOutput schema / $defs / MessageSample / properties / message_type / description
        Removed value: -"ROS2 message type of this sample, e.g. `geometry_msgs/msg/Twist`."
      • removedOutput schema / $defs / MessageSample / properties / message_type / title
        Removed value: -"Message Type"
      • changedOutput schema / $defs / MessageSample / properties / payload / description
        Previous value: -"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."New value: +"Message fields as nested values; `nan`/`inf` are strings. Live `sample_messages` adds `_truncated_fields` (cut paths) and `_raw_text` (unparsable output)."
      • removedOutput schema / $defs / MessageSample / properties / payload / title
        Removed value: -"Payload"
      • changedOutput schema / $defs / MessageSample / properties / received_ns / description
        Previous value: -"Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise."New value: +"Wall-clock ns when the `ros2` CLI printed the message (live `sample_messages` only)."
      • removedOutput schema / $defs / MessageSample / properties / received_ns / title
        Removed value: -"Received Ns"
      • changedOutput schema / $defs / MessageSample / properties / recorded_ns / description
        Previous value: -"Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role."New value: +"Bag record time in ns (`peek_bag_samples` only)."
      • removedOutput schema / $defs / MessageSample / properties / recorded_ns / title
        Removed value: -"Recorded Ns"
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / anyOf
        Removed value: -[
        -  {
        -    "enum": [
        -      "header",
        -      "none",
        -      "recorded"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / default
        Removed value: -null
      • changedOutput schema / $defs / MessageSample / properties / stamp_source / description
        Previous value: -"Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say."New value: +"Origin of `timestamp_ns`: `header`, `payload`, `recorded` (bag record time), `dds_source` or `none` (0)."
      • addedOutput schema / $defs / MessageSample / properties / stamp_source / enum
        Added value: +[
        +  "header",
        +  "payload",
        +  "recorded",
        +  "dds_source",
        +  "none"
        +]
      • removedOutput schema / $defs / MessageSample / properties / stamp_source / title
        Removed value: -"Stamp Source"
      • addedOutput schema / $defs / MessageSample / properties / stamp_source / type
        Added value: +"string"
      • addedOutput schema / $defs / MessageSample / properties / summary
        Added value: +{
        +  "anyOf": [
        +    {
        +      "discriminator": {
        +        "mapping": {
        +          "image": "#/$defs/ImageSummary",
        +          "imu": "#/$defs/ImuSummary",
        +          "laser_scan": "#/$defs/LaserScanSummary",
        +          "odometry": "#/$defs/OdometrySummary",
        +          "point_cloud2": "#/$defs/PointCloud2Summary"
        +        },
        +        "propertyName": "summary_type"
        +      },
        +      "oneOf": [
        +        {
        +          "$ref": "#/$defs/LaserScanSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/OdometrySummary"
        +        },
        +        {
        +          "$ref": "#/$defs/ImuSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/ImageSummary"
        +        },
        +        {
        +          "$ref": "#/$defs/PointCloud2Summary"
        +        }
        +      ]
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Condensed reading of the whole message (see `summary_type`); `null` for other types and DDS peeks."
        +}
      • changedOutput schema / $defs / MessageSample / properties / timestamp_ns / description
        Previous value: -"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."New value: +"Publisher-clock ns: `header.stamp`, else the time in the body of `Clock`, `TFMessage` or `Log`, else 0 (see `stamp_source`); not the arrival time."
      • removedOutput schema / $defs / MessageSample / properties / timestamp_ns / title
        Removed value: -"Timestamp Ns"
      • removedOutput schema / $defs / MessageSample / properties / topic / description
        Removed value: -"Fully qualified topic name the sample was taken from, e.g. `/cmd_vel`."
      • removedOutput schema / $defs / MessageSample / properties / topic / title
        Removed value: -"Topic"
      • changedOutput schema / $defs / MessageSample / required
        Previous value: -[
        -  "topic",
        -  "message_type",
        -  "timestamp_ns"
        -]New value: +[
        +  "topic",
        +  "message_type",
        +  "timestamp_ns",
        +  "stamp_source"
        +]
      • removedOutput schema / $defs / MessageSample / title
        Removed value: -"MessageSample"
      • addedOutput schema / $defs / OdometrySummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "angular_z": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`twist.twist.angular.z`, rad/s."
        +    },
        +    "child_frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`child_frame_id` (frame of the twist)."
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` (frame of the pose)."
        +    },
        +    "linear_speed": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Norm of `twist.twist.linear`, m/s."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when a value is missing; else `null`."
        +    },
        +    "position_x": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.x`, m."
        +    },
        +    "position_y": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.y`, m."
        +    },
        +    "position_z": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`pose.pose.position.z`, m."
        +    },
        +    "summary_type": {
        +      "const": "odometry",
        +      "type": "string"
        +    },
        +    "yaw": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Heading from the pose quaternion, radians."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "child_frame_id",
        +    "linear_speed",
        +    "angular_z",
        +    "position_x",
        +    "position_y",
        +    "position_z",
        +    "yaw"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / PointCloud2Summary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "fields": {
        +      "description": "Point layout.",
        +      "items": {
        +        "$ref": "#/$defs/PointFieldSummary"
        +      },
        +      "type": "array"
        +    },
        +    "frame_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`header.frame_id` of the cloud."
        +    },
        +    "height": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rows (1: unorganized)."
        +    },
        +    "is_dense": {
        +      "anyOf": [
        +        {
        +          "type": "boolean"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "True when no point is invalid."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "One sentence when partial; else `null`."
        +    },
        +    "point_count": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "`width` times `height`."
        +    },
        +    "point_step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bytes per point."
        +    },
        +    "row_step": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bytes per row."
        +    },
        +    "summary_type": {
        +      "const": "point_cloud2",
        +      "type": "string"
        +    },
        +    "width": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Points per row."
        +    }
        +  },
        +  "required": [
        +    "summary_type",
        +    "frame_id",
        +    "width",
        +    "height",
        +    "point_count",
        +    "point_step",
        +    "row_step",
        +    "is_dense",
        +    "fields"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / PointFieldSummary
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "count": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Elements per point."
        +    },
        +    "datatype": {
        +      "description": "Element type, e.g. `FLOAT32`.",
        +      "type": "string"
        +    },
        +    "name": {
        +      "description": "Field name, e.g. `x`.",
        +      "type": "string"
        +    },
        +    "offset": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Byte offset in a point."
        +    }
        +  },
        +  "required": [
        +    "name",
        +    "datatype",
        +    "offset",
        +    "count"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanReturn
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "beam_index": {
        +      "description": "Index in `ranges`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "bearing": {
        +      "description": "Beam direction in radians, sensor frame: 0 is +x, counter-clockwise positive.",
        +      "type": "number"
        +    },
        +    "range": {
        +      "description": "Distance, meters.",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "range",
        +    "bearing",
        +    "beam_index"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanSector
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "beam_count": {
        +      "description": "Beams inside the sector (0: not covered).",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "closest": {
        +      "anyOf": [
        +        {
        +          "$ref": "#/$defs/ScanReturn"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Closest valid return in the sector; `null` with `note`."
        +    },
        +    "note": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Why `closest` is `null`; `null` when it is set."
        +    }
        +  },
        +  "required": [
        +    "beam_count",
        +    "closest"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / ScanSectors
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "front": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings within +-45 degrees of +x."
        +    },
        +    "left": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings from 45 to 135 degrees."
        +    },
        +    "rear": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings beyond +-135 degrees."
        +    },
        +    "right": {
        +      "$ref": "#/$defs/ScanSector",
        +      "description": "Bearings from -135 to -45 degrees."
        +    }
        +  },
        +  "required": [
        +    "front",
        +    "left",
        +    "right",
        +    "rear"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / $defs / TopicRate
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "basis": {
        +      "description": "Clock the rate is measured on: `received_ns` (live) or `recorded_ns` (bag).",
        +      "enum": [
        +        "received_ns",
        +        "recorded_ns"
        +      ],
        +      "type": "string"
        +    },
        +    "interval_cv": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Interval standard deviation over mean; `null` under 2 messages."
        +    },
        +    "interval_median_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Median interval; `null` under 2 messages."
        +    },
        +    "max_gap_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Longest interval; `null` under 2 messages."
        +    },
        +    "mean_interval_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Mean interval; `null` under 2 messages."
        +    },
        +    "message_count": {
        +      "description": "Messages the rate is computed from.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "observed_frequency_hz": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Messages per second on `basis`, drain excluded; the rate the caller received. `null` under 2 messages."
        +    },
        +    "sim_frequency_hz": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Same rate on the messages' own stamps (publisher clock); `null` without advancing stamps."
        +    },
        +    "startup_burst_count": {
        +      "description": "Leading messages set aside as a start-up drain (live only); still in `message_count`.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "trailing_gap_s": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Time from the last message to the end of a live observation; else `null`."
        +    },
        +    "verdict": {
        +      "description": "First match: `silent` (none), `insufficient` (under 5), `intermittent` (an interval over 3x the median), `stable` (cv < 0.2), `jittery` (< 0.5), `erratic`. Describes this window, not a diagnosis.",
        +      "enum": [
        +        "silent",
        +        "insufficient",
        +        "intermittent",
        +        "stable",
        +        "jittery",
        +        "erratic"
        +      ],
        +      "type": "string"
        +    },
        +    "verdict_note": {
        +      "description": "The verdict in plain words.",
        +      "type": "string"
        +    },
        +    "window_s": {
        +      "description": "Observation length, seconds.",
        +      "minimum": 0,
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "basis",
        +    "message_count",
        +    "startup_burst_count",
        +    "window_s",
        +    "mean_interval_s",
        +    "interval_median_s",
        +    "max_gap_s",
        +    "interval_cv",
        +    "observed_frequency_hz",
        +    "sim_frequency_hz",
        +    "trailing_gap_s",
        +    "verdict",
        +    "verdict_note"
        +  ],
        +  "type": "object"
        +}
      • changedOutput schema / description
        Previous value: -"Envelope returned by the `sample_messages` tool."New value: +"Samples returned by `sample_messages`, `peek_dds_samples` and `peek_bag_samples`."
      • changedOutput schema / properties / count / description
        Previous value: -"Number of samples actually returned. May be 0 (no publisher active in live mode, or empty mock fixture), less than the requested count (topic yielded fewer messages within the timeout), or capped by the the silent maximum of 50: request `count > 50` and you will receive at most 50 without warning."New value: +"Number of samples in `samples`: fewer than requested when the topic gave fewer, never more than 50 (a larger request is capped to 50)."
      • removedOutput schema / properties / count / title
        Removed value: -"Count"
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • changedOutput schema / properties / note / description
        Previous value: -"Why `samples` is empty or limited, when the cause is not obvious (for example payload decoding is disabled for DDS user topics). `None` when there is nothing to add."New value: +"Why `samples` is empty or limited, when not obvious; else `None`."
      • removedOutput schema / properties / note / title
        Removed value: -"Note"
      • addedOutput schema / properties / rate
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/TopicRate"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10."
        +}
      • changedOutput schema / properties / samples / description
        Previous value: -"The sampled messages, ordered as received from the backend."New value: +"The samples, in backend order."
      • removedOutput schema / properties / samples / title
        Removed value: -"Samples"
      • removedOutput schema / properties / topic / description
        Removed value: -"Topic the samples were taken from, echoed from the request."
      • removedOutput schema / properties / topic / title
        Removed value: -"Topic"
    • Changedtopic_metrics36 fields changed
      • changedInput schema / properties / domain_id / description
        Previous value: -"Accepted for compatibility (0..232). TopicForge observes the domain it joined at startup (TOPICFORGE_DDS_DOMAIN_ID); this argument does not switch domains, and the response `domain_id` says which one was observed."New value: +"Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` flags a different non-zero value."
      • removedInput schema / properties / domain_id / title
        Removed value: -"Domain Id"
      • changedInput schema / properties / topic / description
        Previous value: -"DDS topic name. Bare DDS names such as `scan` are valid, as are ROS 2 mangled names such as `rt/scan` and the builtin discovery topics `DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`. Letters, digits, `_`, `/` and `::` are allowed; anything else is rejected."New value: +"DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only."
      • removedInput schema / properties / topic / title
        Removed value: -"Topic"
      • addedInput schema / properties / window_s
        Added value: +{
        +  "default": 60,
        +  "description": "Window in seconds, 1..3600, default 60.",
        +  "maximum": 3600,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • removedInput schema / properties / window_seconds
        Removed value: -{
        -  "default": 60,
        -  "description": "Window in seconds over which to compute metrics (1..3600). Defaults to 60 seconds. Smaller windows reflect more recent state; larger windows smooth transient anomalies.",
        -  "maximum": 3600,
        -  "minimum": 1,
        -  "title": "Window Seconds",
        -  "type": "integer"
        -}
      • removedInput schema / title
        Removed value: -"topic_metricsArguments"
      • changedOutput schema / description
        Previous value: -"Temporal metrics for a single DDS topic over a recent window.\n\nBuilt from samples that pass through `peek_dds_samples`. The buffer is\nfilled on demand because neither the `cyclonedds` nor the `fastdds`\nPython binding has a reliable per-sample callback, so a burst between two\ncalls is not seen.\n\nNumeric fields are `None` (`0` for `sequence_gaps_count`) when the data is\nmissing: no samples, no source timestamps, no sequence number in the\npayload. `samples_observed=0` is a valid result, not an error."New value: +"Temporal metrics for a single DDS topic over a recent window."
      • addedOutput schema / properties / declared_frequency_hz
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "`1 / deadline` of the shortest writer Deadline announced: the promised rate, not the observed one; `None` when none announced."
        +}
      • removedOutput schema / properties / frequency_hz_declared
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "number"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Declared, not measured: `1 / deadline` for the shortest QoS Deadline period announced by a writer on this topic in discovery. `None` when no writer announced a finite Deadline or the topic is not announced. It is the rate the application promised, not the rate observed.",
        -  "title": "Frequency Hz Declared"
        -}
      • removedOutput schema / properties / frequency_hz_observed
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "number"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "`samples_observed / window_seconds_actual`. `None` when fewer than 2 samples were observed (a single sample does not define a frequency).",
        -  "title": "Frequency Hz Observed"
        -}
      • changedOutput schema / properties / latency_available / description
        Previous value: -"True when at least one sample in the window carried both a publish timestamp and a receive timestamp. The percentile fields are `None` when this is False."New value: +"True when a sample carried both a publish and a receive timestamp."
      • removedOutput schema / properties / latency_available / title
        Removed value: -"Latency Available"
      • changedOutput schema / properties / latency_ns_p50 / description
        Previous value: -"Median publish-to-receive latency in nanoseconds, computed only when the sample type exposes a publish timestamp (typically via `header.stamp` on `Header`-stamped messages). `None` when `latency_available=False`."New value: +"Median publish-to-receive latency in ns; `None` unless `latency_available`."
      • removedOutput schema / properties / latency_ns_p50 / title
        Removed value: -"Latency Ns P50"
      • removedOutput schema / properties / latency_ns_p95 / title
        Removed value: -"Latency Ns P95"
      • removedOutput schema / properties / latency_ns_p99 / title
        Removed value: -"Latency Ns P99"
      • changedOutput schema / properties / mode_effective / description
        Previous value: -"Runtime mode the adapter served this response in: `live` (real ROS2 introspection) or `mock` (deterministic fixtures). Lets a caller tell a real graph from a demo one without calling `health_check`."New value: +"`live` or `mock` (fixtures)."
      • removedOutput schema / properties / mode_effective / title
        Removed value: -"Mode Effective"
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "One sentence of context, or `None`."
        +}
      • addedOutput schema / properties / observed_frequency_hz
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "`samples_observed / window_actual_s`; `None` under 2 samples."
        +}
      • changedOutput schema / properties / samples_observed / description
        Previous value: -"Number of samples in the buffer matching `topic` within the window. `0` means TopicForge has not seen any sample on this topic recently: it does NOT mean the topic has no publisher, only that no `peek_dds_samples` call captured one in the window."New value: +"Samples buffered in the window. `0` does NOT mean no publisher, only that no `peek_dds_samples` call captured one."
      • removedOutput schema / properties / samples_observed / title
        Removed value: -"Samples Observed"
      • changedOutput schema / properties / sequence_gaps_count / description
        Previous value: -"Number of missing sequence numbers detected in the buffered samples. `0` either means no gaps observed OR the sample type did not expose a sequence number (check `sequence_numbers_available` to disambiguate)."New value: +"Missing sequence numbers in the buffered samples; `0` also when none exist (see `sequence_numbers_available`)."
      • removedOutput schema / properties / sequence_gaps_count / title
        Removed value: -"Sequence Gaps Count"
      • changedOutput schema / properties / sequence_numbers_available / description
        Previous value: -"True when the adapter successfully extracted sequence numbers from at least one sample. Sequence number support depends on the message type: `Header`-stamped messages with a `seq` field expose it; primitives like `std_msgs/String` do not."New value: +"True when a sequence number could be read from a sample."
      • removedOutput schema / properties / sequence_numbers_available / title
        Removed value: -"Sequence Numbers Available"
      • changedOutput schema / properties / status / description
        Previous value: -"How to read the numbers. `unsupported_user_topic`: the topic is a user topic, whose payload is not decoded, so no metric exists and the null fields are not a measurement. `no_samples_yet`: a supported topic with nothing buffered in the window. `ok`: metrics computed from buffered samples."New value: +"`ok`; `no_samples_yet` (empty buffer); `unsupported_user_topic` (payload not decoded: null fields are not measurements)."
      • removedOutput schema / properties / status / title
        Removed value: -"Status"
      • removedOutput schema / properties / topic / description
        Removed value: -"Topic the metrics were computed for."
      • removedOutput schema / properties / topic / title
        Removed value: -"Topic"
      • addedOutput schema / properties / window_actual_s
        Added value: +{
        +  "description": "Seconds actually covered; shorter than `window_s` after a recent start; `0.0` without samples.",
        +  "minimum": 0,
        +  "type": "number"
        +}
      • addedOutput schema / properties / window_s
        Added value: +{
        +  "description": "Requested window in seconds (echoed).",
        +  "maximum": 3600,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • removedOutput schema / properties / window_seconds
        Removed value: -{
        -  "description": "Requested window in seconds (1..3600). Echoed back from the tool call so the LLM can correlate the request.",
        -  "maximum": 3600,
        -  "minimum": 1,
        -  "title": "Window Seconds",
        -  "type": "integer"
        -}
      • removedOutput schema / properties / window_seconds_actual
        Removed value: -{
        -  "description": "Actual elapsed seconds within the window. May be smaller than `window_seconds` when the adapter buffered samples for less time than the requested window (e.g., the server just started). `0.0` when `samples_observed=0`.",
        -  "minimum": 0,
        -  "title": "Window Seconds Actual",
        -  "type": "number"
        -}
      • changedOutput schema / required
        Previous value: -[
        -  "topic",
        -  "window_seconds",
        -  "window_seconds_actual",
        -  "samples_observed",
        -  "mode_effective"
        -]New value: +[
        +  "topic",
        +  "window_s",
        +  "window_actual_s",
        +  "samples_observed",
        +  "mode_effective"
        +]
  3. 4 tool updatesv0.6.3
    • Changedhealth_check1 field changed
      • addedOutput schema / properties / sim_clock_published
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "boolean"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Whether `/clock` has at least one publisher on the ROS 2 graph. True suggests nodes may run on simulated time (`use_sim_time`), so `header.stamp` values are sim time, not wall time (compare with `received_ns`). It is a hint: a publisher on `/clock` does not prove a given node follows it. `None` in mock mode or when the `ros2` CLI could not answer.",
        +  "title": "Sim Clock Published"
        +}
    • Changedpeek_bag_samples5 fields changed
      • changedOutput schema / $defs / MessageSample / properties / payload / description
        Previous value: -"Structured message payload. **In live mode the parser exposes the message fields as positional CSV columns** keyed as `col_0`, `col_1`, ... (`header.stamp.sec`/`nanosec` are stripped out into `timestamp_ns` when detected). The raw CSV row is preserved verbatim under the reserved `_raw_text` key so clients can re-parse against the message's IDL when needed. In mock mode the payload is fully structured and `_raw_text` is absent. Large messages (e.g. images) may be summarized to keep tool output bounded."New value: +"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."
      • addedOutput schema / $defs / MessageSample / properties / received_ns
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise.",
        +  "title": "Received Ns"
        +}
      • addedOutput schema / $defs / MessageSample / properties / recorded_ns
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role.",
        +  "title": "Recorded Ns"
        +}
      • addedOutput schema / $defs / MessageSample / properties / stamp_source
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "header",
        +        "none",
        +        "recorded"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say.",
        +  "title": "Stamp Source"
        +}
      • changedOutput schema / $defs / MessageSample / properties / timestamp_ns / description
        Previous value: -"Timestamp in nanoseconds since epoch. In live mode this is the `header.stamp` of the sampled message when present: the live adapter invokes `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec`/`nanosec` as the first two columns for any `Header`-stamped message. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) carry no embedded timestamp, and `timestamp_ns` falls back to 0; an rclpy-backed adapter will eventually expose rmw receive times for those. Mock mode emits monotonically increasing values for deterministic ordering."New value: +"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."
    • Changedpeek_dds_samples5 fields changed
      • changedOutput schema / $defs / MessageSample / properties / payload / description
        Previous value: -"Structured message payload. **In live mode the parser exposes the message fields as positional CSV columns** keyed as `col_0`, `col_1`, ... (`header.stamp.sec`/`nanosec` are stripped out into `timestamp_ns` when detected). The raw CSV row is preserved verbatim under the reserved `_raw_text` key so clients can re-parse against the message's IDL when needed. In mock mode the payload is fully structured and `_raw_text` is absent. Large messages (e.g. images) may be summarized to keep tool output bounded."New value: +"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."
      • addedOutput schema / $defs / MessageSample / properties / received_ns
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise.",
        +  "title": "Received Ns"
        +}
      • addedOutput schema / $defs / MessageSample / properties / recorded_ns
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role.",
        +  "title": "Recorded Ns"
        +}
      • addedOutput schema / $defs / MessageSample / properties / stamp_source
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "header",
        +        "none",
        +        "recorded"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say.",
        +  "title": "Stamp Source"
        +}
      • changedOutput schema / $defs / MessageSample / properties / timestamp_ns / description
        Previous value: -"Timestamp in nanoseconds since epoch. In live mode this is the `header.stamp` of the sampled message when present: the live adapter invokes `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec`/`nanosec` as the first two columns for any `Header`-stamped message. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) carry no embedded timestamp, and `timestamp_ns` falls back to 0; an rclpy-backed adapter will eventually expose rmw receive times for those. Mock mode emits monotonically increasing values for deterministic ordering."New value: +"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."
    • Changedsample_messages9 fields changed
      • changedInput schema / properties / count / description
        Previous value: -"Maximum number of recent messages to return. Defaults to 5; silently clamped to 50 (the hard cap that keeps tool output bounded; read it from `health_check.max_sample_count`). Negative values raise an error. The returned `SampleResult.count` reflects the actual number of samples produced: it can be lower than the request (empty topic, timeout, mock fixture shorter than requested)."New value: +"Number of messages to wait for. Defaults to 5; at most 50 (a larger request is capped to 50 and `note` says so). 0 returns nothing. `SampleResult.count` is how many arrived: fewer than requested when the topic publishes slowly or the deadline (`timeout_s`) ran out."
      • addedInput schema / properties / count / maximum
        Added value: +50
      • changedInput schema / properties / max_array_length / description
        Previous value: -"Longest array, string or bytes value to return in full, 1..65536; longer ones are cut. A cut array is listed in the sample's `_truncated_after_columns` (index of the last kept column); a cut string or bytes value is kept as its first N characters plus `...` and listed in `_truncated_columns`. Defaults to 128, the `ros2 topic echo` default, which cuts a 541-beam `LaserScan` after 128 ranges. Pass null to return everything in full (large for images and point clouds; a message over the server's size cap, 1 MiB by default, is dropped with a note, and a very large message may not print within the echo timeout)."New value: +"Longest array, string or bytes value to return in full, 1..65536; longer ones are cut after their first N elements or characters and the field's dotted path is listed in the sample's `_truncated_fields`. Defaults to 128, the `ros2 topic echo` default, which cuts a 541-beam `LaserScan` after 128 ranges. Pass null to return everything in full (large for images and point clouds; a message over the server's size cap, 1 MiB by default, is dropped with a note, and a very large message may not print before the deadline)."
      • addedInput schema / properties / timeout_s
        Added value: +{
        +  "default": 10,
        +  "description": "Seconds to wait for the messages, 1..45, default 10. Bounds the whole call, topic lookup and the `ros2` CLI start-up (a few seconds on a slow machine) included: the call returns within about `timeout_s` + 2 s. Whatever arrived by then is returned with a `note`. Raise it for a topic that publishes slower than 1 Hz. The server handles one call at a time, so a long wait delays other tool calls.",
        +  "maximum": 45,
        +  "minimum": 1,
        +  "title": "Timeout S",
        +  "type": "number"
        +}
      • changedOutput schema / $defs / MessageSample / properties / payload / description
        Previous value: -"Structured message payload. **In live mode the parser exposes the message fields as positional CSV columns** keyed as `col_0`, `col_1`, ... (`header.stamp.sec`/`nanosec` are stripped out into `timestamp_ns` when detected). The raw CSV row is preserved verbatim under the reserved `_raw_text` key so clients can re-parse against the message's IDL when needed. In mock mode the payload is fully structured and `_raw_text` is absent. Large messages (e.g. images) may be summarized to keep tool output bounded."New value: +"Message fields as nested named values, e.g. `{\"header\": {\"stamp\": {\"sec\": 12, \"nanosec\": 5}, \"frame_id\": \"base_laser\"}, \"ranges\": [1.0, 1.001]}`. Non-finite floats are the strings `nan`, `inf`, `-inf`. Live `sample_messages` adds `_truncated_fields` (dotted paths of arrays, strings or bytes cut at `max_array_length`; a string of exactly that length plus `...` that was not cut is listed too) when something was cut, and `_raw_text` (the unparsed output) only when a message could not be parsed. With `arrays_summary_only` an array is a string like `<sequence type: float, length: 541>`. Large messages may be dropped to keep tool output bounded."
      • addedOutput schema / $defs / MessageSample / properties / received_ns
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Wall-clock nanoseconds since epoch at which the `ros2` CLI printed this message, after it deserialized and formatted it: not the network receive time, and it lags more for large messages. Accurate to a fraction of a second. Live `sample_messages` only; `None` otherwise.",
        +  "title": "Received Ns"
        +}
      • addedOutput schema / $defs / MessageSample / properties / recorded_ns
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Nanoseconds at which the recorder wrote this message into the bag (the bag's own record time, wall clock unless the recorder ran on simulated time). `peek_bag_samples` only; `None` for live samples, where `received_ns` plays that role.",
        +  "title": "Recorded Ns"
        +}
      • addedOutput schema / $defs / MessageSample / properties / stamp_source
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "header",
        +        "none",
        +        "recorded"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Where `timestamp_ns` comes from: `header` (the message's `header.stamp`), `none` (headerless live message, `timestamp_ns` is 0) or `recorded` (headerless message from a bag, `timestamp_ns` is the bag record time). `None` when the backend does not say.",
        +  "title": "Stamp Source"
        +}
      • changedOutput schema / $defs / MessageSample / properties / timestamp_ns / description
        Previous value: -"Timestamp in nanoseconds since epoch. In live mode this is the `header.stamp` of the sampled message when present: the live adapter invokes `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec`/`nanosec` as the first two columns for any `Header`-stamped message. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) carry no embedded timestamp, and `timestamp_ns` falls back to 0; an rclpy-backed adapter will eventually expose rmw receive times for those. Mock mode emits monotonically increasing values for deterministic ordering."New value: +"Nanoseconds from the message's own top-level `header.stamp` (`stamp_source` `header`), or 0 when the message has no header (`stamp_source` `none`, e.g. `std_msgs/String`, `geometry_msgs/Twist`). It is the publisher's clock, not the arrival time: on simulated time it is sim time since the simulation started (a few seconds, not a date) and 0 is a valid value. From `peek_bag_samples` a message without a header gets the bag record time instead (`stamp_source` `recorded`), also given in `recorded_ns`. Use `received_ns` for when the CLI printed the message. Mock samples with a header carry synthetic increasing values."
  4. 12 tool updatesv0.5.6
    • First observedanalyze_bag
    • First observeddetect_qos_mismatches
    • First observedget_topic_info
    • First observedhealth_check
    • First observedlist_endpoints
    • First observedlist_participants
    • First observedlist_topics
    • First observedparticipant_events
    • First observedpeek_bag_samples
    • First observedpeek_dds_samples
    • First observedsample_messages
    • First observedtopic_metrics

TDQS

A4.4/5.0

Scored across 16 tools

Disambiguation5/5

Tool purposes are clearly separated by resource and backend: ROS 2 graph tools, DDS-layer tools, and bag-analysis tools each have distinct scopes. The main near-overlaps (peek_* and sample_* tools) are explicitly differentiated by live ROS 2, live DDS, or offline bag source, and the descriptions consistently point to the correct alternative.

Naming Consistency4/5

All tool names use snake_case and are readable, but the set mixes verb-first names such as get_topic_info, list_topics, and analyze_bag with noun-phrase names such as health_check, graph_diff, participant_events, and topic_metrics. This is a minor inconsistency rather than a readability problem.

Tool Count4/5

The 16 tools are slightly above the typical 3-15 range, but the breadth is justified by the server covering ROS 2 graph inspection, DDS discovery/QoS, bag analysis, TF diagnostics, health, and graph diffing. Each tool maps to a recognizable diagnostic job and the set does not feel bloated.

Completeness4/5

Core observability lifecycle coverage is strong: graph and endpoint discovery, live and offline sampling, bag summaries, QoS mismatch analysis, TF inspection, metrics, participant events, and health. Minor gaps remain around global service/action discovery, which is only exposed through get_node_info, and richer bag comparison, but there are no obvious dead ends for topic-level debugging.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Read-only Modbus TCP monitoring server that exposes safe MCP tools for AI agents to read holding/input registers, coils, and device identity from industrial devices without write access.
    4
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Read-only MCP server that exposes Autopsy digital forensics case data as tools, enabling LLM clients like Cline to browse filesystems, query artifacts, and search keywords without modifying the case.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A read-only MCP server for bounded semantic inspection of ROS 2 perception systems, enabling discovery and metadata extraction from sensors such as cameras, depth sensors, and LiDARs without device configuration or actuation.
    Apache 2.0