Skip to content

feat(vehicle): verify BLE command timeouts by state#61

Merged
Bre77 merged 3 commits into
mainfrom
fm/tfa-ble-verify-param-k4
Jul 10, 2026
Merged

feat(vehicle): verify BLE command timeouts by state#61
Bre77 merged 3 commits into
mainfrom
fm/tfa-ble-verify-param-k4

Conversation

@Bre77

@Bre77 Bre77 commented Jul 10, 2026

Copy link
Copy Markdown
Member

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

  • Added an opt-in verify_commands path for BLE vehicles that, on mutating command timeouts, verifies clearly derivable post-state before returning success or re-raising BluetoothTimeout.
  • Threaded the verify_commands constructor option through Bluetooth vehicle factories while preserving compatible Teslemetry and Tessie createBluetooth signatures.
  • Documented BLE command verification behavior and added mocked transport coverage for verified, unverified, disabled, timeout, and happy-path cases.

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=1

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=1

Pipeline

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.py
  • PYTHONPATH=tests uv run python - <<'PY' | tee /tmp/no-mistakes-evidence/01KX56Z7J678SPV4VXSVE5SM05/ble_verify_commands_transcript.txt
  • uv run pytest tests/test_ble_mocked_commands.py tests/test_cross_transport_parity.py
  • uv run pytest tests
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

firstmate crewmate 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
@Bre77
Bre77 merged commit 0e7bce4 into main Jul 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant