ITADN

joint_limits accepts a nan bound at construction, then silently drops every inbound joint_command

#2171Opensundargthb 创建于 7 天前
S
sundargthbcommented
## Problem `RosTelemetryBase._validate_joint_limits` exists so a malformed bound refuses the bridge at construction. Its own docstring states the reason: > Failing fast here (rather than per-command) means a malformed bound surfaces at > bridge construction, not as a silent mid-run rejection of every command. It does not achieve that for a non-finite bound. The only ordering check is `if low > high`, and **every comparison against `nan` is `False`**, so `(-1.9, nan)` passes validation. `_command_action` then evaluates `low <= pos <= high`, which is also `False` for every position, so the bridge drops **every inbound `joint_command`** for that joint - the exact failure mode the guard was written to prevent, and the one it reports as a successful construction. `inf` bounds pass for the same reason and are equally unusable: `(inf, inf)` admits nothing. ## Measured `strands_robots.ros_telemetry.RosTelemetryBase`, in-process, no ROS 2 needed (both methods are static/classmethod). An in-range command of `0.5`: | `joint_limits` | construction | command `0.5` | |---|---|---| | `{"shoulder_pan": (-1.9, 1.9)}` | accepted | applied | | `{"shoulder_pan": (-1.9, nan)}` | **accepted** | **dropped, every time** | | `{"shoulder_pan": (nan, nan)}` | **accepted** | **dropped, every time** | | `{"shoulder_pan": (inf, inf)}` | **accepted** | **dropped, every time** | | `{"shoulder_pan": (1.9, -1.9)}` | refused (`min > max`) | - | ```python from strands_robots.ros_telemetry import RosTelemetryBase as R nan = float("nan") print(1.9 > nan) # False <- why the ordering check passes class Msg: name = ["shoulder_pan"] position = [0.5] norm = R._validate_joint_limits({"shoulder_pan": (-1.9, nan)}) # accepted print(R._command_action(R, Msg(), joint_limits=norm)) # None ``` The runtime log is honest about the drop but not about the cause - it prints the `nan` as if it were a declared range an operator chose: ``` rejecting joint_command - shoulder_pan=0.5000 outside declared range [-1.9000, nan] (whole command dropped, no partial application) ``` ## Why it matters This is the guard on the inbound command surface of a **physical** arm. The symptom is a live, correctly-constructed bridge that silently ignores every command from an external ROS 2 stack - and the operator's evidence is a bridge that came up clean. It is the same class as #1754 (a non-finite value admitted by a guard that checked coercion but not finiteness, then reporting success); that one poisoned state, this one silently refuses all actuation. ## Fix `strands_robots.utils.finite_number_error` is already the shared domain guard for exactly this - a signed finite physical quantity - with 75 call sites, so this is a use of the existing rule rather than a new one (AGENTS.md convention 11: a value-domain guard becomes shared when it has a second caller; this is the Nth). Apply it to `low` and `high` before the ordering check, so a non-finite bound is refused with the same wording as everywhere else. Both hardware bridges (`HardwareRosBridge`, `HardwareRtpsBridge`) inherit this validator, so one fix covers both transports. ## Test `tests/` pin: a `nan` bound, an `inf` bound, and `nan`/`inf` on either side each raise `ValueError` at `_validate_joint_limits`, and the valid-range case still admits an in-range command. Fails on pre-fix code (currently all four are accepted). Found while verifying prose for the ROS 2 section of the fleet-orchestration blog against the code: the draft claimed malformed limits are refused at build time, which is what the docstring promises and is true for every malformed bound except a non-finite one.
0 条评论