refactor(rtps): the participant lives below both callers that publish through it - #3864
Conversation
… through it RtpsRobot reached a DDS writer by importing the use_rtps @tool, the last drivers|mesh -> tools inversion that was a transport rather than a verb package. The participant - one DomainParticipant, writers and readers cached per (topic, type), the IDL sample builder and the action dispatch - moves to strands_robots/rtps/participant.py, beside the mangling and the IDL bundle it is built on. The tool keeps what an agent envelope owns: the numeric-option domains, the operator gate and the docstring a model reads. rtps_action takes the operator gate as a required keyword argument, so a caller cannot publish to a blocklisted surface by forgetting one, and both callers key it with the same GATE_TOOL label - one interrupt id and one audit source for a command that reaches the same physical topic either way.
yinsong1986
left a comment
There was a problem hiding this comment.
Summary
Moves the DDS mechanics (shared DomainParticipant, per-(topic, type) writer/reader caches, IDL sample builder, action dispatch) out of the use_rtps @tool into strands_robots/rtps/participant.py, so RtpsRobot publishes through the participant directly instead of importing an agent tool from the layer above — eliminating the last drivers|mesh -> tools transport inversion. The safety contract is preserved and strengthened: rtps_action takes the operator gate as a required keyword-only argument (no caller can silently omit it), both callers key it with the same GATE_TOOL label so one blocklisted topic files one interrupt id and one audit row, the gate is consulted after the backend probe and before the process-wide lock, and topic/type validation via ros_topic_error/dds_type_name now sits in the participant where both callers hit it. The moved code is byte-equivalent to what lived in the tool; behavior visible to callers is unchanged.
What's good
- The required-
gatesignature plus thenever_gatedsingleton makes the un-gated-caller failure mode unrepresentable, andtests/test_every_ros_transport_consults_the_command_gate.pypins both the signature (keyword-only, no default) and byte-identical operator prompts across the tool and robot surfaces. - The layer contract table in
tests/test_import_layers_are_a_dag.pygains artps.participantrow with an equality assertion, so moving the participant back up intotoolsfails a test rather than silently restoring the inversion. - The obsolete
mesh.rtps_robot -> tools.use_rtpsexception is removed fromscripts/check_import_layers.pyrather than left as a vestige. - Test seams were migrated consistently (
_backend,time.sleep,rtps_actionpatch points all repointed to the participant module) and the pre-fix control run documented in the description shows the new pins fail onmain.
strands-labs#3864 landed the sibling RTPS cut on main. Both changes coexist: the layer roster carries a row per shared transport, the cross-transport gate suite becomes one table with a row per transport that has a second caller, and the recorded-call comparisons drop the gate, which is a fresh closure per call and never on the wire.
RtpsRobotopened a DDS writer by importing theuse_rtps@tool- the lastdrivers|mesh -> toolsinversion that was a transport rather than a verb package. The participant (oneDomainParticipant, writers/readers cached per(topic, type), the IDL sample builder, the action dispatch) moves tostrands_robots/rtps/participant.py, beside the mangling and IDL bundle it is built on. The tool keeps what an agent envelope owns: the numeric-option domains, the operator gate, the docstring a model reads.drivers|mesh -> toolstools/use_rtps.py+139 package lines are the new module's own contract: why it sits there, the
GATE_TOOLlabel, andrtps_action's requiredgateargument. Serves #3818 0.8-0.9 (layered DAG, zero cycles as a test).Safety is unchanged and now has one owner.
rtps_actiontakes the operator gate as a required keyword argument - a caller cannot publish to a blocklisted surface by forgetting one - and both callers pass the sameGATE_TOOLlabel, so onecmd_velincident files one interrupt id and one audit source whichever surface asked.Tests. Pre-fix control (new test files on a clean
mainworktree): 3 failed / 19 passed, and the cross-transport gate suite cannot import the participant at all; after: 22 / 33 passed. New pins - artps.participantrow in the layer-contract table (placement, nothing read above its layer, callers in two layers), the required-gatesignature, and a robot publish prompting byte-identically to the tool's.scripts/check_whole_tree_graders.py6196 passed / 77 skipped;tests/mesh tests/tools tests/rtps+ the root graders 10048 passed / 24 skipped; ruff + mypy clean on 2,205 files.