Collector & probes¶
Modules: collector.py, live.py, enhanced.py, remote.py, redact.py
The collector turns "this machine" into a snapshot: a JSON document of facts that the rule engine can evaluate and that can be saved, redacted and compared with another machine.
flowchart LR
subgraph probes["Probes"]
env["Environment variables<br/>ROS_*, RMW_*, DDS XML paths"]
net["ip -j addr<br/>interfaces + multicast"]
ns["/proc/self/ns/*<br/>container detection"]
rosgraph["ros2 node list<br/>ros2 topic list -t<br/>ros2 daemon status<br/>(parallel threads)"]
end
probes --> snap[("Snapshot<br/>schema_version 2")]
doctor["ros2 doctor --report<br/>(effective RMW, cached 60 s)"] --> overview
docker["docker ps"] --> overview
shm["/dev/shm checks"] --> overview
snap --> overview["Overview response"]
Probes¶
Only the three ros2 graph probes run in parallel threads; the environment, network and namespace probes run
sequentially (they are fast).
| Probe | Command / source | Timeout | Notes |
|---|---|---|---|
| Environment | os.environ |
— | Only an allow-list of ROS/DDS variables is recorded |
| DDS config files | CYCLONEDDS_URI, FASTDDS_DEFAULT_PROFILES_FILE |
— | Records whether the file exists; never reads its content |
| Network | ip -j addr show |
3 s | Name, state, MTU, multicast flag, addresses |
| Container | /.dockerenv, /proc/1/cgroup, /proc/self/ns/{ipc,net,pid} |
— | Namespace IDs make IPC comparisons possible |
| ROS graph | ros2 node list / topic list with --no-daemon --spin-time N |
5 s + N | N = AUTODIAG_DISCOVERY_SECONDS (default 2) |
| Effective RMW | ros2 doctor --report |
15 s | Output cap raised to 500 kB; result cached 60 s |
| Docker | docker ps --format '{{json .}}' |
5 s | Optional |
| Topic inspection | ros2 topic info --verbose |
8 s + N | Parsed into endpoints with QoS |
| Sample | ros2 topic echo --once |
1–15 s | Optional reliability override; output capped at 16 kB |
Rules for every probe¶
- No shell. Commands run as argument lists through
subprocess.run; topic names are validated against a strict pattern before use. - Bounded. Every call has a timeout and an output cap. A timeout is reported as a state, never as a crash.
- Daemon-independent.
ROS2CLI_DISABLE_DAEMON=1and--no-daemonavoid stale daemon caches. - Read-only. No probe starts, stops or reconfigures anything.
Why wait for discovery?¶
With --no-daemon, the ros2 CLI creates a fresh DDS participant per call. Without a spin time it often returns
before discovery completes, and node and topic counts change between refreshes. The configurable wait trades a
couple of seconds for consistent results; see ADR-007.
Remote snapshots¶
remote.py fetches a peer's snapshot from /api/agent/snapshot:
- numeric private or loopback IPs only, no hostnames (avoids DNS-based tricks);
- proxies disabled and redirects refused, so the token can't leak;
- response capped at 300 kB and schema-checked.
Redaction¶
redact.py walks the snapshot and replaces hostnames, IPv4/IPv6 addresses and home paths with salted SHA-256
tokens (host-1a2b3c4d). Loopback and multicast addresses are kept because they carry diagnostic meaning.
collect --redact uses a random salt unless --salt is given, and prints it to stderr. The same salt on two machines
produces the same tokens, so redacted snapshots still compare correctly.
mask_for_cloud() is the lighter variant for AI prompts: it masks node names (publisher 1), this machine's hostname,
IP addresses and home paths, and keeps QoS, topic names and message types.