feat(vehicle): verify BLE command timeouts by state#61
Merged
Conversation
added 3 commits
July 10, 2026 15:09
A BluetoothTimeout from a mutating BLE command is inconclusive: the vehicle can execute the command without its ack reaching the client. Add an opt-in VehicleBluetooth constructor knob (verify_commands, default off) that resolves that ambiguity where it belongs - inside the BLE class. On a timeout from a mutating command whose expected post-state is derivable from its arguments, the same held connection reads the mapped prover state and returns a normal success result if it matches or re-raises the timeout if not. Verify-on-timeout only, so the happy path keeps returning on the terminal ack with no extra read. Commands whose outcome cannot be derived or read (toggles, relative volume steps, ack-only actions) re-raise unchanged. Interception is at the _sendVehicleSecurity/_sendInfotainment seam (each called once per public mutation; reads go through _get*, so a verification read never recurses). Covered commands: lock/unlock, set_charge_limit, set_charging_amps, adjust_volume (absolute), set_temps, auto_conditioning_start/stop. The VCSEC lock prover reads while asleep; INFO provers need the car awake and never wake it - an asleep-car read failure re-raises the original timeout. With this enabled, VehicleRouter's blind BLE->cloud failover no longer double-executes non-idempotent commands: a verified-executed command returns success (no failover) and a verified-not-executed one raises (failover is safe), so the Router needs no changes. - threaded through Vehicles/VehiclesBluetooth.createBluetooth (default off) - mocked-transport tests for executed/not-executed/unverifiable/disabled paths and happy-path timing (no verification read on a normal ack) - docs/bluetooth_vehicles.md documents the knob and prover table
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Add an opt-in constructor knob to VehicleBluetooth (verify_commands, default off, backward compatible) that resolves the false-negative-ack problem inside the BLE class, where it belongs. Background: a BluetoothTimeout from a mutating BLE command is INCONCLUSIVE - the vehicle can execute the command without its ack reaching the client (live-proven: door_lock/unlock timed out yet actuated). Downstream this causes VehicleRouter's blind BLE->cloud failover to double-execute non-idempotent commands. With verification inside VehicleBluetooth, a verified-executed command returns success (no failover) and a verified-not-executed one raises (failover is genuinely safe), so the Router needs no changes.
Design decisions made: (1) Verify-on-timeout ONLY, not verify-always - post-PR-59 the happy path returns on the terminal ack almost instantly, so paying the prover read only in the ambiguous timeout case preserves speed ('faster and still reliable'). (2) Intercept at the _sendVehicleSecurity/_sendInfotainment seam because each is called exactly once per public mutation and carries the semantic proto (command+args), while reads go through _get*, so a verification read never recurses into verification and no ~100 command methods need editing. (3) Expected post-state is derived from the command's own proto args (lock->LOCKED, set_charge_limit(80)->limit 80, adjust_volume(v)->volume v). (4) Only clearly-derivable, absolute commands are covered: door_lock/unlock (VCSEC vehicle_state lock field), set_charge_limit / set_charging_amps (charge_state), adjust_volume absolute (media_state), set_temps / auto_conditioning_start/stop (climate_state). True toggles, relative volume steps (media_volume_up/down), and ack-only actions (flash_lights, honk_horn, closures, wake) deliberately have no plan and re-raise the timeout unchanged. (5) The prover read rides the same held connection and never wakes the car; VCSEC lock prover is readable while asleep, INFO provers need the car awake and an asleep-car read failure re-raises the original timeout. (6) Semantics: verified-executed returns a shape-consistent success dict, verified-not-executed raises BluetoothTimeout, unverifiable raises - never a partial/ambiguous return.
Scope: threaded verify_commands through Vehicles/VehiclesBluetooth.createBluetooth (default off); NotImplementedError createBluetooth overrides in teslemetry/tessie vehicle updated only to keep the signature compatible for pyright strict. Added mocked-transport tests (tests/test_ble_command_verification.py) covering verified-executed returns success, verified-not-executed raises, unverifiable raises, default-off byte-identical + no verification read, prover-read-timeout re-raises, and happy-path timing (no verification read on a normal ack). Documented the knob and prover table in docs/bluetooth_vehicles.md and the design rationale in AGENTS.md. Live car verification was NOT required or performed - the mocked transport exercises the machinery. Two teslemetry/tessie files show pre-existing ruff-format drift on unrelated lines that I deliberately left untouched to keep the diff focused.
What Changed
verify_commandspath for BLE vehicles that, on mutating command timeouts, verifies clearly derivable post-state before returning success or re-raisingBluetoothTimeout.verify_commandsconstructor option through Bluetooth vehicle factories while preserving compatible Teslemetry and TessiecreateBluetoothsignatures.Risk Assessment
✅ Low: The change is narrowly scoped to an opt-in BLE timeout verification path, preserves default behavior, and the verifier mappings align with the surrounding command/state-reader structure.
Testing
Focused verification tests, adjacent BLE/parity tests, and the full suite all passed; the evidence transcript directly exercises the user-facing BLE command outcomes with mocked transport, and transient test-created worktree artifacts were cleaned up.
Evidence: BLE verification transcript
factory knob: vehicles.create(..., verify_commands=True) -> vehicle.verify_commands=True verified executed: door_lock() -> {'response': {'result': True, 'reason': ''}} ; sends=2 verified mismatch: set_charge_limit(80) -> BluetoothTimeout ; sends=2 default off: door_lock() -> BluetoothTimeout ; sends=1 happy path: set_charge_limit(80) -> {'response': {'result': True, 'reason': ''}} ; sends=1Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
uv run pytest tests/test_ble_command_verification.pyPYTHONPATH=tests uv run python - <<'PY' | tee /tmp/no-mistakes-evidence/01KX56Z7J678SPV4VXSVE5SM05/ble_verify_commands_transcript.txtuv run pytest tests/test_ble_mocked_commands.py tests/test_cross_transport_parity.pyuv run pytest tests✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.