from __future__ import annotations import json import os import tempfile import unittest from pathlib import Path from unittest.mock import patch from cognitive_agent.application.capability_registry import CapabilityRegistry from cognitive_agent.application.native_can_intent import merge_native_can_intent from cognitive_agent.application.native_can_runtime import ( NativeCanCompileError, NativeCanExecutor, NativeCanRuntime, ) from cognitive_agent.infrastructure.can.gateway import CanFrame REPOSITORY_ROOT = Path(__file__).resolve().parents[2] class _FakeBus: def __init__( self, received: list[CanFrame] | None = None, *, fail_on_calls: set[int] | None = None, ) -> None: self.received = list(received or []) self.fail_on_calls = set(fail_on_calls or set()) self.send_calls = 0 self.sent: list[CanFrame] = [] self.closed = False def send( self, arbitration_id: int, data: bytes, *, is_fd: bool = False, bitrate_switch: bool = False, ) -> CanFrame: self.send_calls += 1 if self.send_calls in self.fail_on_calls: raise RuntimeError(f"injected send fault {self.send_calls}") 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: return self.received.pop(0) if self.received else None def close(self) -> None: self.closed = True def _native_action( capability: str, parameters: dict, target: str = "driver" ) -> dict: return { "domain": "seat", "capability": capability, "target": target, "operation": "set", "parameters": parameters, } class DriverSeatNativeCanTests(unittest.TestCase): def setUp(self) -> None: self.runtime = NativeCanRuntime(REPOSITORY_ROOT) def _compile( self, capability: str, parameters: dict, target: str = "driver" ) -> tuple[dict, dict]: config = self.runtime.build_function_config( "seat", [_native_action(capability, parameters, target)], "req-driver-seat", "local_rules", ) return config, self.runtime.compile(config) @staticmethod def _parsed_action(utterance: str) -> dict | None: parsed = merge_native_can_intent({}, utterance) actions = parsed.get("native_can_actions", []) return actions[0] if len(actions) == 1 else None def test_catalog_exposes_only_driver_and_front_left_native_targets(self) -> None: snapshot = CapabilityRegistry(REPOSITORY_ROOT, {"seat"}).snapshot() native_seat = next( item for item in snapshot["domains"] if item["domain"] == "seat" and item["availability"] == "native_can" ) capabilities = {item["name"]: item for item in native_seat["capabilities"]} self.assertEqual( set(capabilities), { "seat.set_driver_ventilation_native", "seat.set_driver_heating_native", "seat.set_driver_massage_native", "seat.set_driver_axis_native", "seat.set_driver_position_native", "seat.recall_driver_memory_native", }, ) for capability in capabilities.values(): self.assertEqual(set(capability["target_scopes"]), {"driver", "front_left"}) def test_exact_commands_preserve_verified_driver_seat_parameters(self) -> None: cases = { "seat driver vent low": ( "seat.set_driver_ventilation_native", {"state": "on", "level": 2}, "driver", ), "seat front-left heat high": ( "seat.set_driver_heating_native", {"state": "on", "level": 1}, "front_left", ), "seat driver massage off": ( "seat.set_driver_massage_native", {"action": "off"}, "driver", ), "seat driver massage strength strong": ( "seat.set_driver_massage_native", {"action": "strength", "strength": 3}, "driver", ), "seat driver massage mode relax": ( "seat.set_driver_massage_native", {"action": "mode", "mode": "relax"}, "driver", ), "seat driver backrest backward 800": ( "seat.set_driver_axis_native", {"axis": "backrest", "direction": "backward", "duration_ms": 800}, "driver", ), "seat driver height stop": ( "seat.set_driver_axis_native", {"axis": "height", "direction": "stop", "duration_ms": 500}, "driver", ), "seat driver position 3": ( "seat.set_driver_position_native", {"slot": 3}, "driver", ), "seat driver memory recall 2": ( "seat.recall_driver_memory_native", {"slot": 2}, "driver", ), } for utterance, expected in cases.items(): with self.subTest(utterance=utterance): action = self._parsed_action(utterance) self.assertIsNotNone(action) assert action is not None self.assertEqual( (action["capability"], action["parameters"], action["target"]), expected, ) def test_chinese_commands_require_explicit_driver_scope(self) -> None: cases = { "打开主驾座椅通风": ( "seat.set_driver_ventilation_native", {"state": "on", "level": 2}, ), "主驾座椅加热高档": ( "seat.set_driver_heating_native", {"state": "on", "level": 1}, ), "把驾驶位按摩调成波浪模式": ( "seat.set_driver_massage_native", {"action": "mode", "mode": "wave"}, ), "主驾座椅按摩强度调高": ( "seat.set_driver_massage_native", {"action": "strength", "strength": 3}, ), "主驾座椅向前移动500毫秒": ( "seat.set_driver_axis_native", {"axis": "horizontal", "direction": "forward", "duration_ms": 500}, ), "主驾座椅靠背后仰800毫秒": ( "seat.set_driver_axis_native", {"axis": "backrest", "direction": "backward", "duration_ms": 800}, ), "把主驾座椅坐盆降低": ( "seat.set_driver_axis_native", {"axis": "basin", "direction": "down", "duration_ms": 500}, ), "主驾座椅升高": ( "seat.set_driver_axis_native", {"axis": "height", "direction": "up", "duration_ms": 500}, ), "主驾座椅调整到记忆位置3": ( "seat.recall_driver_memory_native", {"slot": 3}, ), } for utterance, expected in cases.items(): with self.subTest(utterance=utterance): action = self._parsed_action(utterance) self.assertIsNotNone(action) assert action is not None self.assertEqual((action["capability"], action["parameters"]), expected) self.assertEqual(action["target"], "driver") def test_parser_rejects_passenger_right_front_and_unsafe_guesses(self) -> None: utterances = ( "seat passenger vent low", "seat front-right heat high", "打开副驾座椅通风", "打开右前座椅加热", "打开主驾和副驾座椅通风", "seat driver vent low, seat passenger vent low", "打开主驾座椅按摩", "主驾座椅向前移动50毫秒", "seat driver height forward 500", ) for utterance in utterances: with self.subTest(utterance=utterance): parsed = merge_native_can_intent({}, utterance) self.assertFalse(parsed.get("native_can_actions")) def test_deterministic_driver_parse_supersedes_deferred_model_copy(self) -> None: parsed = { "actions": [], "domain_actions": [], "native_can_actions": [], "deferred_requests": [ { "domain": "seat", "capability": "seat.set_driver_ventilation_native", "target_scope": "driver", "parameters": {"state": "on", "level": 2}, "execution_backend": "native_can", } ], "model_requests": [ { "domain": "seat", "capability": "seat.set_driver_ventilation_native", "target_scope": "driver", "operation": "set", "parameters": [ {"name": "state", "value": "on"}, {"name": "level", "value": 2}, ], "reason": "model", } ], } merged = merge_native_can_intent(parsed, "打开主驾座椅通风") self.assertEqual(len(merged["native_can_actions"]), 1) self.assertEqual( merged["native_can_actions"][0]["capability"], "seat.set_driver_ventilation_native", ) def test_thermal_frames_match_cpp_whitelist_and_feedback_contract(self) -> None: cases = ( ( "seat.set_driver_ventilation_native", {"state": "on", "level": 1}, "00 01 00 00 00 00 00 00", "driver_seat_ventilation:3", ), ( "seat.set_driver_ventilation_native", {"state": "off"}, "00 03 00 00 00 00 00 00", "driver_seat_ventilation:1", ), ( "seat.set_driver_heating_native", {"state": "on", "level": 2}, "00 20 00 00 00 00 00 00", "driver_seat_heating:2", ), ( "seat.set_driver_heating_native", {"state": "off"}, "00 30 00 00 00 00 00 00", "driver_seat_heating:1", ), ) for capability, parameters, payload, expected in cases: with self.subTest(capability=capability, parameters=parameters): _, plan = self._compile(capability, parameters) command = plan["spec"]["commands"][0] self.assertEqual(command["signals"]["frame_id"], "0x431") self.assertEqual(command["signals"]["data_hex"], payload) self.assertTrue(command["signals"]["is_fd"]) self.assertTrue(command["signals"]["bitrate_switch"]) self.assertEqual(command["repeat"], {"count": 3, "period_ms": 100}) self.assertEqual(command["signals"]["feedback_ids"], "0x4FA") self.assertEqual(command["signals"]["expected_feedback"], expected) def test_massage_frames_match_cpp_whitelist(self) -> None: cases = ( ({"action": "off"}, "12 00 00 01 00 00 00 00", "driver_seat_massage_mode:1"), ( {"action": "strength", "strength": 3}, "12 03 00 00 00 00 00 00", "driver_seat_massage_strength:11", ), ( {"action": "mode", "mode": "relax"}, "12 00 00 06 00 00 00 00", "driver_seat_massage_mode:6", ), ) for parameters, payload, expected in cases: with self.subTest(parameters=parameters): _, plan = self._compile("seat.set_driver_massage_native", parameters) command = plan["spec"]["commands"][0] self.assertEqual(command["signals"]["frame_id"], "0x4C1") self.assertEqual(command["signals"]["data_hex"], payload) self.assertEqual(command["repeat"], {"count": 3, "period_ms": 100}) self.assertEqual(command["signals"]["feedback_ids"], "0x4FA") self.assertEqual(command["signals"]["expected_feedback"], expected) def test_all_four_axes_compile_bounded_motion_and_forced_stop(self) -> None: cases = ( ("horizontal", "forward", "05 01 00 00 00 00 00 00"), ("height", "down", "05 08 00 00 00 00 00 00"), ("backrest", "forward", "05 10 00 00 00 00 00 00"), ("basin", "down", "05 80 00 00 00 00 00 00"), ) for axis, direction, payload in cases: with self.subTest(axis=axis): _, plan = self._compile( "seat.set_driver_axis_native", {"axis": axis, "direction": direction, "duration_ms": 500}, ) command = plan["spec"]["commands"][0] self.assertEqual(command["signals"]["frame_id"], "0x4C1") self.assertEqual(command["signals"]["data_hex"], payload) self.assertEqual(command["repeat"], {"count": 5, "period_ms": 100}) self.assertEqual(command["signals"]["feedback_ids"], "0x494") self.assertEqual(command["signals"]["special_handler"], "driver_seat_axis") safety = json.loads(command["signals"]["special_parameters"]) self.assertEqual(safety["stop_data_hex"], "05 00 00 00 00 00 00 00") self.assertEqual(safety["stop_repeat_count"], 3) self.assertEqual(safety["stop_period_ms"], 100) public = self.runtime.command_frame_public(command) self.assertTrue(public["safety"]["forced_stop"]) def test_explicit_axis_stop_is_three_fd_stop_frames(self) -> None: _, plan = self._compile( "seat.set_driver_axis_native", {"axis": "height", "direction": "stop"}, ) command = plan["spec"]["commands"][0] self.assertEqual(command["signals"]["data_hex"], "05 00 00 00 00 00 00 00") self.assertEqual(command["repeat"], {"count": 3, "period_ms": 100}) self.assertNotIn("special_handler", command["signals"]) def test_memory_aliases_compile_the_same_learned_twenty_frame_sequence(self) -> None: commands = [] for capability in ( "seat.set_driver_position_native", "seat.recall_driver_memory_native", ): with self.subTest(capability=capability): _, plan = self._compile(capability, {"slot": 3}) command = plan["spec"]["commands"][0] commands.append(command) self.assertEqual(command["signals"]["frame_id"], "0x4C1") self.assertEqual( command["signals"]["special_handler"], "driver_seat_memory_recall", ) special = json.loads(command["signals"]["special_parameters"]) self.assertEqual(len(special["frame_sequence"]), 20) self.assertEqual( special["frame_sequence"][0::2], ["16 9A 66 3C 76 04 06 00"] * 10, ) self.assertEqual( special["frame_sequence"][1::2], ["15 02 C6 3E 2A C8 FF FF"] * 10, ) self.assertEqual(special["period_ms"], 50) public = self.runtime.command_frame_public(command) self.assertEqual(public["sequence"]["frame_count"], 20) self.assertTrue( public["sequence"]["requires_fresh_initialized_0x494"] ) self.assertEqual( commands[0]["signals"]["special_parameters"], commands[1]["signals"]["special_parameters"], ) def test_memory_executor_requires_initialized_status_and_alternates_frames(self) -> None: config, plan = self._compile( "seat.recall_driver_memory_native", {"slot": 2} ) initialized = CanFrame( 0x494, bytes((0, 0, 0, 0, 0, 0, 0, 0x3C)), channel=1, is_fd=True, bitrate_switch=True, ) bus = _FakeBus([initialized, initialized]) with patch("cognitive_agent.application.native_can_runtime.time.sleep"): report = NativeCanExecutor( lambda: bus, feedback_timeout_ms=1, feedback_required=False ).execute(self.runtime, plan, config) self.assertEqual(report["spec"]["status"], "succeeded") self.assertEqual(len(bus.sent), 20) self.assertTrue(all(frame.is_fd and frame.bitrate_switch for frame in bus.sent)) self.assertEqual( [frame.data[0] for frame in bus.sent], [0x16, 0x15] * 10 ) self.assertEqual( [frame.data for frame in bus.sent[0::2]], [bytes.fromhex("16 9A 66 3C 76 04 06 00")] * 10, ) self.assertEqual( [frame.data for frame in bus.sent[1::2]], [bytes.fromhex("15 02 A6 AE 00 C8 FF FF")] * 10, ) telemetry = report["spec"]["results"][0]["telemetry"] self.assertFalse(telemetry["physical_effect_confirmed"]) self.assertEqual(len(telemetry["sent_frames"]), 20) uninitialized_bus = _FakeBus( [CanFrame(0x494, bytes(8), channel=1, is_fd=True)] ) rejected = NativeCanExecutor( lambda: uninitialized_bus, feedback_timeout_ms=1, feedback_required=False, ).execute(self.runtime, plan, config) self.assertEqual(rejected["spec"]["status"], "failed") self.assertFalse(uninitialized_bus.sent) def test_memory_sequence_stops_immediately_on_send_failure(self) -> None: config, plan = self._compile( "seat.set_driver_position_native", {"slot": 1} ) initialized = CanFrame( 0x494, bytes((0, 0, 0, 0, 0, 0, 0, 0x3C)), channel=1 ) bus = _FakeBus([initialized], fail_on_calls={4}) with patch("cognitive_agent.application.native_can_runtime.time.sleep"): report = NativeCanExecutor( lambda: bus, feedback_timeout_ms=1, feedback_required=False ).execute(self.runtime, plan, config) self.assertEqual(report["spec"]["status"], "failed") self.assertEqual(bus.send_calls, 4) self.assertEqual(len(bus.sent), 3) def test_memory_compile_rejects_missing_or_malformed_learned_slots(self) -> None: with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "driver_memory.json" path.write_text( json.dumps( { "version": 1, "positions": { "1": { "payload_15": "15 00", "payload_16": "16 00 00 00 00 00 00 00", } }, } ), encoding="utf-8", ) with patch.dict( os.environ, {"COGNITIVE_AGENT_DRIVER_SEAT_MEMORY_PATH": str(path)}, ): config = self.runtime.build_function_config( "seat", [_native_action("seat.recall_driver_memory_native", {"slot": 1})], "req-bad-memory", "local_rules", ) with self.assertRaisesRegex(NativeCanCompileError, "8-byte"): self.runtime.compile(config) missing = self.runtime.build_function_config( "seat", [_native_action("seat.recall_driver_memory_native", {"slot": 2})], "req-missing-memory", "local_rules", ) with self.assertRaisesRegex(NativeCanCompileError, "has not been learned"): self.runtime.compile(missing) def test_compiler_fails_closed_for_wrong_seat_or_invalid_parameters(self) -> None: for target in ("passenger", "front_right"): action = _native_action( "seat.set_driver_ventilation_native", {"state": "on", "level": 2}, target, ) with self.subTest(target=target): self.assertFalse(self.runtime.supports(action)) with self.assertRaises(NativeCanCompileError): self.runtime.build_function_config( "seat", [action], "req-reject", "local_rules" ) self.assertNotIn("seat.set_ventilation", self.runtime.supported_capabilities) invalid = ( ( "seat.set_driver_ventilation_native", {"state": "on", "level": 3}, ), ( "seat.set_driver_massage_native", {"action": "mode", "mode": "unknown"}, ), ( "seat.set_driver_axis_native", {"axis": "height", "direction": "forward", "duration_ms": 500}, ), ( "seat.set_driver_axis_native", {"axis": "horizontal", "direction": "forward", "duration_ms": 2001}, ), ) for capability, parameters in invalid: with self.subTest(capability=capability, parameters=parameters): config = self.runtime.build_function_config( "seat", [_native_action(capability, parameters)], "req-invalid", "local_rules", ) with self.assertRaises(NativeCanCompileError): self.runtime.compile(config) def test_thermal_feedback_is_semantically_matched_and_decoded(self) -> None: config, plan = self._compile( "seat.set_driver_ventilation_native", {"state": "on", "level": 1}, ) feedback = CanFrame( 0x4FA, bytes((0x0B, 0, 0, 0, 0, 0x81, 0, 0)), channel=1, is_fd=True, bitrate_switch=True, ) bus = _FakeBus([feedback]) with patch("cognitive_agent.application.native_can_runtime.time.sleep"): report = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1).execute( self.runtime, plan, config ) result = report["spec"]["results"][0] self.assertEqual(report["spec"]["status"], "succeeded") self.assertTrue(result["telemetry"]["feedback_match"]) self.assertEqual(result["telemetry"]["feedback_state"]["ventilation"], "high") self.assertEqual(result["telemetry"]["feedback_state"]["heating"], "off") self.assertEqual(result["telemetry"]["feedback_state"]["massage_mode"], "off") self.assertTrue(bus.closed) wrong_bus = _FakeBus( [CanFrame(0x4FA, bytes((0x0A, 0, 0, 0, 0, 0x81, 0, 0)), channel=1)] ) with patch("cognitive_agent.application.native_can_runtime.time.sleep"): wrong_report = NativeCanExecutor( lambda: wrong_bus, feedback_timeout_ms=1 ).execute(self.runtime, plan, config) self.assertEqual(wrong_report["spec"]["status"], "failed") self.assertFalse( wrong_report["spec"]["results"][0]["telemetry"]["feedback_match"] ) def test_massage_feedback_matches_mode_and_strength_fields(self) -> None: cases = ( ( {"action": "mode", "mode": "relax"}, bytes((1, 0, 0, 0, 0, 0x86, 0, 0)), "relax", "off", ), ( {"action": "strength", "strength": 3}, bytes((1, 0, 0, 0, 0, 0xB1, 0, 0)), "off", "strong", ), ) for parameters, payload, mode, strength in cases: with self.subTest(parameters=parameters): config, plan = self._compile( "seat.set_driver_massage_native", parameters ) bus = _FakeBus([CanFrame(0x4FA, payload, channel=1)]) with patch("cognitive_agent.application.native_can_runtime.time.sleep"): report = NativeCanExecutor( lambda: bus, feedback_timeout_ms=1 ).execute(self.runtime, plan, config) telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(report["spec"]["status"], "succeeded") self.assertTrue(telemetry["feedback_match"]) self.assertEqual(telemetry["feedback_state"]["massage_mode"], mode) self.assertEqual( telemetry["feedback_state"]["massage_strength"], strength ) def test_axis_executor_sends_stop_and_reports_0x494_limitation(self) -> None: config, plan = self._compile( "seat.set_driver_axis_native", {"axis": "horizontal", "direction": "forward", "duration_ms": 500}, ) feedback = CanFrame( 0x494, bytes((0, 0, 0, 0, 0, 0, 0, 0x3C)), channel=1, is_fd=True, bitrate_switch=True, ) bus = _FakeBus([feedback]) with patch("cognitive_agent.application.native_can_runtime.time.sleep"): report = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1).execute( self.runtime, plan, config ) # 0x494 proves only initialization and cannot confirm that the timed # motion physically occurred, so strict hardware feedback fails closed. self.assertEqual(report["spec"]["status"], "failed") self.assertEqual(len(bus.sent), 8) self.assertTrue(all(frame.is_fd and frame.bitrate_switch for frame in bus.sent)) self.assertEqual( [frame.data for frame in bus.sent[-3:]], [bytes((0x05, 0, 0, 0, 0, 0, 0, 0))] * 3, ) telemetry = report["spec"]["results"][0]["telemetry"] self.assertIsNone(telemetry["feedback_match"]) self.assertTrue(telemetry["feedback_required"]) self.assertTrue(telemetry["feedback_observed"]) self.assertEqual( telemetry["feedback_state"]["execution_confirmation"], "status_frame_only", ) self.assertTrue(all(telemetry["feedback_state"]["initialized"].values())) def test_axis_executor_attempts_every_stop_frame_after_motion_fault(self) -> None: config, plan = self._compile( "seat.set_driver_axis_native", {"axis": "basin", "direction": "up", "duration_ms": 500}, ) bus = _FakeBus(fail_on_calls={2}) with patch("cognitive_agent.application.native_can_runtime.time.sleep"): report = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1).execute( self.runtime, plan, config ) self.assertEqual(report["spec"]["status"], "failed") self.assertEqual(bus.send_calls, 5) self.assertEqual( [frame.data for frame in bus.sent[-3:]], [bytes((0x05, 0, 0, 0, 0, 0, 0, 0))] * 3, ) self.assertIn("motion send failed", report["spec"]["results"][0]["message"]) self.assertTrue(bus.closed) if __name__ == "__main__": unittest.main()