from __future__ import annotations import unittest from unittest.mock import patch from cognitive_agent.application.native_can_feedback import ( decode_feedback, feedback_matches, ) from cognitive_agent.application.native_can_intent import ( _parse_exact_command, merge_native_can_intent, ) from cognitive_agent.application.native_can_runtime import ( NativeCanCompileError, NativeCanExecutor, NativeCanRuntime, ) from cognitive_agent.application.seat_service import REPOSITORY_ROOT from cognitive_agent.infrastructure.can.gateway import CanFrame from cognitive_agent.runtime.contracts import ContractValidator class _FakeBus: def __init__( self, received: list[CanFrame] | None = None, *, fail_send_calls: set[int] | None = None, ) -> None: self.received = list(received or []) self.fail_send_calls = set(fail_send_calls or set()) self.send_attempts: list[tuple[int, bytes, bool, bool]] = [] self.sent: list[CanFrame] = [] self.events: list[str] = [] self.debug_commands: list[str] = [] self.closed = False def set_debug_command(self, command: str) -> None: self.debug_commands.append(command) def send( self, arbitration_id: int, data: bytes, *, is_fd: bool = False, bitrate_switch: bool = False, ) -> CanFrame: self.events.append("send") self.send_attempts.append( (arbitration_id, bytes(data), is_fd, bitrate_switch) ) call_number = len(self.send_attempts) if call_number in self.fail_send_calls: raise RuntimeError(f"injected send failure {call_number}") frame = CanFrame( arbitration_id, bytes(data), channel=1, is_fd=is_fd, bitrate_switch=bitrate_switch, ) self.sent.append(frame) return frame def receive(self, timeout_ms: int, *, include_fd: bool = False) -> CanFrame | None: self.events.append("receive") return self.received.pop(0) if self.received else None def close(self) -> None: self.closed = True class VerifiedBodyCanTests(unittest.TestCase): def setUp(self) -> None: self.runtime = NativeCanRuntime(REPOSITORY_ROOT) def compile_action( self, domain: str, capability: str, target: str, parameters: dict ) -> dict: config = self.runtime.build_function_config( domain, [ { "domain": domain, "capability": capability, "target": target, "operation": "set", "parameters": parameters, } ], "verified-body-can", "local_rules", ) return self.runtime.compile(config)["spec"]["commands"][0] @staticmethod def shade_feedback(position: int, motion: int = 1) -> CanFrame: data = bytearray(8) data[1] = position data[3] = motion << 3 return CanFrame(0x41A, bytes(data), channel=1) def assert_fd_brs_triplet(self, command: dict, frame_id: str) -> None: self.assertEqual(command["signals"]["frame_id"], frame_id) self.assertTrue(command["signals"]["is_fd"]) self.assertTrue(command["signals"]["bitrate_switch"]) self.assertEqual(command["repeat"], {"count": 3, "period_ms": 100}) def test_indoor_and_all_reading_lights_use_only_verified_4f5(self) -> None: cases = ( ("interior_light.set_indoor_power", "vehicle", "on", "00 00 03 FF FF 00 08 00"), ("interior_light.set_indoor_power", "vehicle", "off", "00 00 03 FF FF 00 04 00"), ("interior_light.set_reading_power", "all", "on", "00 00 03 FF FF 00 08 00"), ("interior_light.set_reading_power", "all", "off", "00 00 03 FF FF 00 04 00"), ) for capability, target, state, expected_hex in cases: with self.subTest(capability=capability, state=state): command = self.compile_action( "interior_light", capability, target, {"state": state} ) self.assert_fd_brs_triplet(command, "0x4F5") self.assertEqual(command["signals"]["data_hex"], expected_hex) self.assertEqual(command["signals"]["feedback_ids"], "0x42E") self.assertEqual( command["signals"]["expected_feedback"], f"indoor_light:{state}", ) def test_unverified_light_controls_are_not_hardware_capabilities(self) -> None: rejected = ( ("interior_light.set_wall_power", "vehicle", {"state": "on"}), ("ambient_light.set_mode", "vehicle", {"mode": "cinema"}), ( "ambient_light.set_illumination", "vehicle", {"color_hex": "#203040", "brightness_pct": 40}, ), ) for capability, target, parameters in rejected: action = {"capability": capability, "target": target, "parameters": parameters} with self.subTest(capability=capability): self.assertFalse(self.runtime.supports(action)) with self.assertRaises(NativeCanCompileError): self.runtime.build_function_config( capability.split(".", 1)[0], [action], "reject-unverified-light", "local_rules", ) zoned = { "capability": "interior_light.set_reading_power", "target": "front_left", "parameters": {"state": "on"}, } self.assertFalse(self.runtime.supports(zoned)) with self.assertRaises(NativeCanCompileError): config = self.runtime.build_function_config( "interior_light", [zoned], "reject-zoned-reading", "local_rules" ) self.runtime.compile(config) def test_exact_parser_exposes_only_verified_light_vocabulary(self) -> None: self.assertEqual(_parse_exact_command("light wall on"), []) self.assertEqual(_parse_exact_command("light reading front-left on"), []) self.assertEqual(_parse_exact_command("light ambient scene cinema"), []) self.assertEqual(_parse_exact_command("light ambient rgb 32 64 96 40"), []) color = _parse_exact_command("light ambient color 70") self.assertEqual(color[0]["capability"], "ambient_light.set_color_index") self.assertEqual(color[0]["parameters"], {"color_index": 70}) brightness = _parse_exact_command("light ambient brightness 5") self.assertEqual( brightness[0]["capability"], "ambient_light.set_brightness_level" ) self.assertEqual(brightness[0]["parameters"], {"brightness_level": 5}) def test_fully_exact_ac_power_is_the_only_executable_plan(self) -> None: for requested_state in ("on", "off"): opposite_state = "off" if requested_state == "on" else "on" for model_capability, model_state in ( ("climate.set_ac", requested_state), ("climate.set_ac", opposite_state), ("climate.set_power", opposite_state), ): with self.subTest( requested_state=requested_state, model_capability=model_capability, model_state=model_state, ): model_request = { "domain": "climate", "capability": model_capability, "target_scope": "vehicle", "operation": "set", "parameters": [{"name": "state", "value": model_state}], } routed_action = { "domain": "climate", "capability": model_capability, "target": "vehicle", "operation": "set", "parameters": {"state": model_state}, } parsed = { "detected_domains": ["climate", "window", "seat"], "actions": [ { "target": "passenger", "function": "ventilation", "state": "on", } ], "model_requests": [model_request], "domain_actions": [ routed_action, { "domain": "window", "capability": "window.set_motion", "target": "front_left", "operation": "set", "parameters": {"motion": "open"}, }, ], "native_can_actions": [routed_action], "deferred_requests": [routed_action], "preset_actions": [{"target": "passenger"}], "preset_events": [{"action": "apply"}], "ambiguities": [ {"field": "requests[0].target", "question": "hallucinated"} ], } merged = merge_native_can_intent( parsed, f"ac {requested_state}" ) self.assertEqual(merged["actions"], []) self.assertEqual(merged["domain_actions"], []) self.assertEqual(merged["preset_actions"], []) self.assertEqual(merged["preset_events"], []) self.assertEqual(merged["ambiguities"], []) self.assertEqual(merged["detected_domains"], ["climate"]) self.assertEqual(len(merged["model_requests"]), 1) self.assertEqual(len(merged["deferred_requests"]), 1) self.assertEqual(len(merged["native_can_actions"]), 1) for key in ( "model_requests", "deferred_requests", "native_can_actions", ): self.assertEqual( merged[key][0]["capability"], "climate.set_power" ) parameters = merged[key][0]["parameters"] if isinstance(parameters, list): parameters = { item["name"]: item["value"] for item in parameters } self.assertEqual(parameters, {"state": requested_state}) natural_model_action = { "domain": "climate", "capability": "climate.set_ac", "target": "vehicle", "operation": "set", "parameters": {"state": "on"}, } natural = merge_native_can_intent( { "model_requests": [natural_model_action], "domain_actions": [natural_model_action], "native_can_actions": [], "deferred_requests": [], }, "打开空调并关闭遮阳帘", ) self.assertIn(natural_model_action, natural["domain_actions"]) self.assertGreaterEqual(len(natural["native_can_actions"]), 2) mixed_shade_action = { "domain": "roof", "capability": "roof.set_shade_motion", "target": "vehicle", "operation": "set", "parameters": {"motion": "close"}, } mixed = merge_native_can_intent( { "model_requests": [], "domain_actions": [mixed_shade_action], "native_can_actions": [], "deferred_requests": [], }, "ac on,关闭遮阳帘", ) self.assertIn(mixed_shade_action, mixed["domain_actions"]) self.assertEqual( [item["capability"] for item in mixed["native_can_actions"]], ["climate.set_power"], ) def test_vehicle_lock_is_observational_only_and_fails_closed(self) -> None: action = { "domain": "door", "capability": "door.set_vehicle_lock", "target": "vehicle", "operation": "set", "parameters": {"state": "lock"}, } catalog_entry = self.runtime.capabilities[action["capability"]] self.assertEqual(catalog_entry["execution_support"], "observational_only") self.assertFalse(catalog_entry["physical_control_supported"]) self.assertNotIn(action["capability"], self.runtime.supported_capabilities) self.assertFalse(self.runtime.supports(action)) with self.assertRaisesRegex( NativeCanCompileError, r"unsupported.*current CH1.*0x496.*observational only", ): self.runtime.build_function_config( "door", [action], "retired-vehicle-lock", "local_rules" ) with self.assertRaisesRegex( NativeCanCompileError, r"unsupported.*current CH1.*observational only" ): self.runtime._frame_specs({"capability": action["capability"]}) vehicle = _parse_exact_command("door vehicle lock") self.assertEqual(vehicle[0]["capability"], "door.set_vehicle_lock") self.assertEqual(vehicle[0]["target"], "vehicle") def test_rear_lock_compiles_only_the_matrix_backed_second_row_request(self) -> None: action = { "domain": "door", "capability": "door.set_rear_lock", "target": "rear", "operation": "set", "parameters": {"state": "lock"}, } catalog_entry = self.runtime.capabilities[action["capability"]] self.assertEqual( catalog_entry["execution_support"], "matrix_candidate_with_feedback" ) self.assertTrue(catalog_entry["physical_control_supported"]) self.assertIn(action["capability"], self.runtime.supported_capabilities) self.assertTrue(self.runtime.supports(action)) cases = ( ("lock", "0A 40 00 00 00 00 00 00"), ("unlock", "0A 80 00 00 00 00 00 00"), ) for state, payload in cases: with self.subTest(state=state): command = self.compile_action( "door", "door.set_rear_lock", "rear", {"state": state} ) self.assertEqual(command["signals"]["frame_id"], "0x32B") self.assertEqual(command["signals"]["data_hex"], payload) self.assertFalse(command["signals"]["is_fd"]) self.assertFalse(command["signals"]["bitrate_switch"]) self.assertNotIn("repeat", command) self.assertEqual(command["signals"]["feedback_ids"], "0x407") self.assertEqual( command["signals"]["expected_feedback"], f"rear_lock:{state}" ) exact = _parse_exact_command(f"door rear {state}") self.assertEqual(exact[0]["capability"], "door.set_rear_lock") self.assertEqual(exact[0]["target"], "rear") def test_climate_cycle_uses_verified_1de_bits_and_404_feedback(self) -> None: cases = ( ("internal", "00 00 08 00 00 00 00 00"), ("external", "00 00 10 00 00 00 00 00"), ) for mode, payload in cases: with self.subTest(mode=mode): command = self.compile_action( "climate", "climate.set_cycle_mode", "vehicle", {"mode": mode}, ) self.assertEqual(command["signals"]["frame_id"], "0x1DE") self.assertEqual(command["signals"]["data_hex"], payload) self.assertEqual(command["repeat"], {"count": 3, "period_ms": 100}) self.assertEqual(command["signals"]["feedback_ids"], "0x404") self.assertEqual( command["signals"]["expected_feedback"], f"climate_cycle:{mode}", ) def test_ambient_controls_use_only_verified_3fc_layouts(self) -> None: cases = ( ("ambient_light.set_power", {"state": "on"}, "00 36 00 75 00 80 00 00", "ambient_power:on"), ("ambient_light.set_power", {"state": "off"}, "00 31 00 75 00 80 00 00", "ambient_power:off"), ("ambient_light.set_color_index", {"color_index": 70}, "46 30 00 75 00 00 00 00", "ambient_color:70"), ("ambient_light.set_brightness_level", {"brightness_level": 1}, "00 32 00 75 00 00 00 00", "ambient_brightness:1"), ("ambient_light.set_brightness_level", {"brightness_level": 5}, "00 36 00 75 00 00 00 00", "ambient_brightness:5"), ) for capability, parameters, payload, expected_feedback in cases: with self.subTest(capability=capability, parameters=parameters): command = self.compile_action( "ambient_light", capability, "vehicle", parameters ) self.assert_fd_brs_triplet(command, "0x3FC") self.assertEqual(command["signals"]["data_hex"], payload) self.assertEqual(command["signals"]["feedback_ids"], "0x42E") self.assertEqual( command["signals"]["expected_feedback"], expected_feedback ) def test_verified_ambient_function_configs_pass_domain_schema(self) -> None: validator = ContractValidator(REPOSITORY_ROOT) for capability, parameters in ( ("ambient_light.set_color_index", {"color_index": 70}), ("ambient_light.set_brightness_level", {"brightness_level": 5}), ): with self.subTest(capability=capability): config = self.runtime.build_function_config( "ambient_light", [ { "domain": "ambient_light", "capability": capability, "target": "vehicle", "operation": "set", "parameters": parameters, } ], "verified-ambient-schema", "local_rules", ) self.assertEqual(validator.validate(config), []) def test_ambient_ranges_and_parameter_shapes_fail_closed(self) -> None: for capability, parameters in ( ("ambient_light.set_color_index", {"color_index": 0}), ("ambient_light.set_color_index", {"color_index": 128}), ("ambient_light.set_brightness_level", {"brightness_level": 0}), ("ambient_light.set_brightness_level", {"brightness_level": 6}), ( "ambient_light.set_color_index", {"color_index": 70, "brightness_level": 3}, ), ): with self.subTest(capability=capability, parameters=parameters): with self.assertRaises(NativeCanCompileError): self.compile_action("ambient_light", capability, "vehicle", parameters) def test_shade_presets_and_arbitrary_position_never_use_4f5(self) -> None: for position, payload in ( (0, "20 00 00 00 00 00 00 00"), (50, "30 00 00 00 00 00 00 00"), (100, "10 00 00 00 00 00 00 00"), ): with self.subTest(position=position): command = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": position} ) self.assertEqual(command["signals"]["frame_id"], "0x431") self.assertEqual(command["signals"]["data_hex"], payload) self.assertFalse(command["signals"]["is_fd"]) self.assertEqual(command["signals"]["feedback_ids"], "0x41A") self.assertEqual(command["signals"]["special_handler"], "shade_preset") self.assertEqual( command["signals"]["debug_command"], f"shade position {position}", ) arbitrary = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": 37} ) self.assertEqual(arbitrary["signals"]["frame_id"], "0x431") self.assertNotEqual(arbitrary["signals"]["frame_id"], "0x4F5") self.assertEqual(arbitrary["signals"]["special_handler"], "shade_position") self.assertEqual(arbitrary["signals"]["debug_command"], "shade position 37") special = arbitrary["signals"]["special_parameters"] self.assertIn('"fresh_feedback_timeout_ms": 2500', special) self.assertIn('"stop_data_hex": "40 00 00 00 00 00 00 00"', special) def test_executor_attaches_compiled_debug_command_to_mqtt_style_bus(self) -> None: config = self.runtime.build_function_config( "roof", [ { "domain": "roof", "capability": "roof.set_shade_position", "target": "vehicle", "operation": "set", "parameters": {"position_pct": 100}, } ], "shade-debug-command", "local_rules", ) plan = self.runtime.compile(config) bus = _FakeBus([self.shade_feedback(100, motion=1)]) executor = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1) report = executor.execute(self.runtime, plan, config) self.assertEqual(report["spec"]["status"], "succeeded") self.assertEqual(bus.debug_commands, ["shade position 100"]) def test_shade_preset_executor_waits_for_authoritative_target(self) -> None: command = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": 100} ) reached = self.shade_feedback(99, motion=1) bus = _FakeBus([reached]) executor = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1) sent, feedback = executor._execute_shade_preset(bus, command) self.assertIs(feedback, reached) self.assertEqual(len(sent), 1) self.assertEqual( bus.send_attempts, [(0x431, bytes.fromhex("10 00 00 00 00 00 00 00"), False, False)], ) def test_shade_preset_forces_stop_when_target_feedback_is_still_moving(self) -> None: command = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": 0} ) reached_while_closing = self.shade_feedback(2, motion=3) stopped = self.shade_feedback(2, motion=1) bus = _FakeBus([reached_while_closing, stopped]) executor = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1) sent, feedback = executor._execute_shade_preset(bus, command) self.assertIs(feedback, stopped) self.assertEqual(len(sent), 2) self.assertEqual( [attempt[1] for attempt in bus.send_attempts], [ bytes.fromhex("20 00 00 00 00 00 00 00"), bytes.fromhex("40 00 00 00 00 00 00 00"), ], ) def test_arbitrary_shade_position_reads_fresh_state_then_stops(self) -> None: command = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": 37} ) before = self.shade_feedback(20, motion=1) reached = self.shade_feedback(38, motion=2) stopped = self.shade_feedback(38, motion=1) bus = _FakeBus([before, reached, stopped]) executor = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1) sent, feedback = executor._execute_shade_position(bus, command) self.assertIs(feedback, stopped) self.assertEqual(bus.events[0], "receive") self.assertEqual( [attempt[1] for attempt in bus.send_attempts], [ bytes.fromhex("10 00 00 00 00 00 00 00"), bytes.fromhex("40 00 00 00 00 00 00 00"), ], ) self.assertTrue(all(not attempt[2] and not attempt[3] for attempt in bus.send_attempts)) self.assertEqual(len(sent), 2) def test_arbitrary_shade_position_always_stops_after_feedback_fault(self) -> None: command = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": 37} ) bus = _FakeBus() executor = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1) before = self.shade_feedback(20, motion=1) with patch.object( executor, "_wait_for_id", side_effect=[before, RuntimeError("injected receive failure")], ): with self.assertRaisesRegex(RuntimeError, "injected receive failure"): executor._execute_shade_position(bus, command) self.assertEqual( [attempt[1] for attempt in bus.send_attempts], [ bytes.fromhex("10 00 00 00 00 00 00 00"), bytes.fromhex("40 00 00 00 00 00 00 00"), ], ) def test_arbitrary_shade_start_failure_still_attempts_stop(self) -> None: command = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": 37} ) bus = _FakeBus([self.shade_feedback(20, motion=1)], fail_send_calls={1}) executor = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1) with self.assertRaisesRegex(RuntimeError, "injected send failure"): executor._execute_shade_position(bus, command) self.assertEqual( [attempt[1] for attempt in bus.send_attempts], [ bytes.fromhex("10 00 00 00 00 00 00 00"), bytes.fromhex("40 00 00 00 00 00 00 00"), ], ) def test_arbitrary_shade_missing_or_invalid_feedback_never_starts_motion(self) -> None: command = self.compile_action( "roof", "roof.set_shade_position", "vehicle", {"position_pct": 37} ) executor = NativeCanExecutor(lambda: _FakeBus(), feedback_timeout_ms=1) for before in (None, self.shade_feedback(255, motion=1)): bus = _FakeBus() with self.subTest(before=before): with patch.object(executor, "_wait_for_id", return_value=before): with self.assertRaisesRegex( RuntimeError, "0x41A|新鲜|有效|fresh|valid" ): executor._execute_shade_position(bus, command) self.assertEqual(bus.send_attempts, []) def test_door_feedback_enums_and_shade_stop_match_reference(self) -> None: door_data = bytearray(8) # 0x407: front-left unlock, front-right lock, rear-left lock, # rear-right unlock; whole-vehicle logical state lock. door_data[0] = 0b01101001 door_data[1] = 0b00001000 decoded = decode_feedback(CanFrame(0x407, bytes(door_data), channel=1)) locks = decoded["domain_state"]["door"]["locks"] self.assertEqual(locks["front_left"]["state"], "unlock") self.assertEqual(locks["front_right"]["state"], "lock") self.assertEqual(locks["rear_left"]["state"], "lock") self.assertEqual(locks["rear_right"]["state"], "unlock") self.assertEqual( decoded["domain_state"]["door"]["vehicle_lock"]["state"], "lock" ) all_locked = bytearray(8) all_locked[0] = 0xAA all_locked[1] = 0x08 locked_frame = CanFrame(0x407, bytes(all_locked), channel=1) self.assertTrue(feedback_matches("vehicle_lock:lock", locked_frame)) logical_only = bytearray(all_locked) logical_only[0] = 0xA9 self.assertFalse( feedback_matches( "vehicle_lock:lock", CanFrame(0x407, bytes(logical_only), channel=1), ) ) all_unlocked = bytearray(8) all_unlocked[0] = 0x55 all_unlocked[1] = 0x04 self.assertTrue( feedback_matches( "vehicle_lock:unlock", CanFrame(0x407, bytes(all_unlocked), channel=1), ) ) shade_data = bytearray(8) shade_data[1] = 37 shade_data[3] = 1 << 3 shade = CanFrame(0x41A, bytes(shade_data), channel=1) self.assertTrue(feedback_matches("shade_stopped", shade)) if __name__ == "__main__": unittest.main()