Repository navigation
OpenPAVE v1.8.2 — Body-side safety (superseded)
⚠️ Superseded — use v1.8.4. This release has
known issues that were found afterwards and fixed inv1.8.3andv1.8.4; they are listed below.
OpenPAVE v1.8.2 — Body-side safety
Safety fixes for a body that is left moving when its brain goes away. No new feature surface; the seam, the
{action, params} contract and the registries are unchanged.
Why
Until now every STOP guarantee lived on the brain. A brain stopped with kill, an SSH session that
dropped, a crashed or kill -9ed brain, or a lost network link all left a body that had been told to
trot marching with nobody to stop it — a locomotion verb persists until a stop arrives.
Safety fixes
- STOP on
SIGTERMandSIGHUP, not onlyCtrl+C. Python only runs cleanup forSIGINT; the other two
killedrun_live/run_inferenceoutright.pave_runtime/shutdown.pynow turns all three into a clean
shutdown whosefinallySTOPs the body (exit code128 + signal).
(shutdown.py) - An unconfirmed STOP is loud. If the final STOP does not complete, the driver exits
5
(safety-critical) instead of passing silently. The--allow-motionlease now also covers the motion send
itself, so a signal that lands mid-send still ends in a STOP. - Body-side dead-man lease — every
trot/movethe body accepts carries a lease
(BODY_MOTION_TTL_S, default 5 s); if the brain does not renew it, the body STOPs itself. There is
no brain-supplied TTL, so a buggy brain cannot ask for a longer one. Renewal is re-sending the same motion;
run_liverenews every 2 s andrun_inference --allow-motionrenews during its hold. Shared byraw_zenoh
anddevice_connect.
(lease.py) - A body process being shut down leaves its actuator stopped —
seam_cli.py servesendsstopto its
adapter onSIGTERM/SIGHUP/Ctrl+C.
Behaviour changes
- A one-shot
seam_cli.py send trotno longer marks time until astop: it is never renewed, so the body
stops it after about 5 s. Userun_live.py, orrun_inference.py --allow-motion --motion-hold-seconds N. run_live/run_inference/seam_cli serveexit128 + signalwhen ended by a signal.
Verification
- 193 unit tests, including tests that send real
SIGINT/SIGTERM/SIGHUPto the real CLI entry
points. The concurrency tests were mutation-checked. - Real
raw_zenohwith a mock body: after the brain waskill -9ed mid-motion the body stopped itself in
4.2 s on one host and 3.0 s from a DGX brain to a Raspberry Pi 5 over the LAN, and was not restarted.
Expect 3–5 s.
Not verified: a physical actuator or the real dog (the cross-host run used a dry-run servo); device_connect's
serve() wiring against a live runtime. Known limit: the lease lives in the body process — if that
process itself crashes, a separate controller (PuppyPi's puppy_control) keeps running.
Known issues found after release
- A shutdown signal could be lost on Python before 3.12 —
run_livebounded its awaits with
asyncio.wait_for, which can swallow a cancellation that lands in the same event-loop iteration in which the
inner call finishes. The signal was then ignored and the loop could only be ended withSIGKILL. The window is
small and Python 3.12 (as on the DGX Spark) is not affected. Fixed in v1.8.3. - A STOP could wait behind a lease renewal hanging on the network, up to the 5 s send timeout.
Fixed in v1.8.3. - A command that did not reach the body was never sent again —
run_livemarked an action as sent before the
send, so after a lost packet (or a body that was not up yet) the edge-triggered loop never retried it. This
predatesv1.8.2(present sincev1.8.0). Fixed in v1.8.4. - A repeat of a motion that failed on the body was answered "completed (renewed)" without being run.
Fixed in v1.8.4.
This note was added afterwards: the v1.8.2 tag itself does not contain a release-note file.
Full notes: docs/releases/v1.8.2.md ·
compare v1.8.1…v1.8.2