from __future__ import annotations import unittest from unittest.mock import patch from cognitive_agent.application.native_can_feedback import ( VehicleSafetyPreflightError, observe_vehicle_safety, require_stationary_vehicle, ) from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService from cognitive_agent.infrastructure.can.gateway import CanFrame from cognitive_agent.infrastructure.can.hardware import HardwareSafetyError class _ReceiveOnlyBus: def __init__(self, frames: list[CanFrame]) -> None: self.frames = list(frames) self.send_called = False def send(self, *args, **kwargs): # pragma: no cover - a safety tripwire self.send_called = True raise AssertionError("passive safety preflight must never transmit") def receive(self, timeout_ms: int, *, include_fd: bool = False): return self.frames.pop(0) if self.frames else None def close(self) -> None: return None class _Bridge: def __init__(self, bus: _ReceiveOnlyBus) -> None: self.bus = bus self.closed = False def channel_bus(self, channel: int) -> _ReceiveOnlyBus: if channel != 1: raise AssertionError(f"unexpected channel {channel}") return self.bus def close(self) -> None: self.closed = True def _gear(raw: int, *, channel: int = 1) -> CanFrame: data = bytearray(8) data[3] = raw & 0x0F return CanFrame(0x342, bytes(data), channel=channel) def _speed(raw: int, *, channel: int = 1) -> CanFrame: data = bytearray(8) data[0] = 0x07 data[1] = raw & 0xFF data[2] = (raw >> 8) & 0x0F return CanFrame(0x4EB, bytes(data), channel=channel) class VehicleSafetyPreflightTests(unittest.TestCase): def test_fresh_ch1_p_and_zero_speed_pass_without_transmitting(self) -> None: bus = _ReceiveOnlyBus([_gear(1), _speed(0)]) snapshot = observe_vehicle_safety(bus, timeout_ms=100) require_stationary_vehicle(snapshot) self.assertTrue(snapshot["stationary"]) self.assertEqual(snapshot["evidence"]["gear"]["frame_id"], "0x342") self.assertEqual(snapshot["evidence"]["speed"]["frame_id"], "0x4EB") self.assertFalse(bus.send_called) def test_ch0_frames_cannot_satisfy_the_gate(self) -> None: snapshot = observe_vehicle_safety( _ReceiveOnlyBus([_gear(1, channel=0), _speed(0, channel=0)]), timeout_ms=100, ) self.assertFalse(snapshot["complete"]) self.assertEqual(snapshot["ignored_wrong_channel"], 2) with self.assertRaisesRegex( VehicleSafetyPreflightError, "0x342 gear evidence" ): require_stationary_vehicle(snapshot) def test_drive_or_nonzero_speed_fails_closed(self) -> None: for frames, message in ( ([_gear(4), _speed(0)], "not confirmed in P"), ([_gear(1), _speed(10)], "not confirmed at 0 km/h"), ): with self.subTest(message=message): snapshot = observe_vehicle_safety( _ReceiveOnlyBus(frames), timeout_ms=100 ) with self.assertRaisesRegex(VehicleSafetyPreflightError, message): require_stationary_vehicle(snapshot) def test_policy_cannot_be_redirected_to_ch0(self) -> None: with self.assertRaisesRegex(VehicleSafetyPreflightError, "only CH1"): observe_vehicle_safety( _ReceiveOnlyBus([_gear(1, channel=0), _speed(0, channel=0)]), timeout_ms=100, required_channel=0, ) def test_service_refuses_before_any_command_when_vehicle_is_not_in_p(self) -> None: service = SeatDemoService(REPOSITORY_ROOT) action = { "domain": "climate", "capability": "climate.set_power", "target": "vehicle", "operation": "set", "parameters": {"state": "on"}, } config = service.native_can_runtime.build_function_config( "climate", [action], "req-safety-preflight", "local_rules" ) plan = service.native_can_runtime.compile(config) bus = _ReceiveOnlyBus([_gear(4), _speed(0)]) bridge = _Bridge(bus) with patch.dict( "os.environ", {"COGNITIVE_AGENT_SHARED_CAN_RUNTIME_ENABLED": "false"}, clear=False, ), patch.object( type(service.vehicle_can_settings), "bridge", return_value=bridge ): with self.assertRaisesRegex(HardwareSafetyError, "not confirmed in P"): service._execute_orchestrated_reports( {"native_climate": config}, {"native_climate": plan}, {"native_climate"}, {}, hardware_request=True, simulated_hardware_request=False, simulation_fault="normal", ) self.assertFalse(bus.send_called) self.assertTrue(bridge.closed) if __name__ == "__main__": unittest.main()