feat: synchronize CH1 vehicle control and feedback

This commit is contained in:
vee
2026-08-11 10:42:13 +08:00
parent 3701192a11
commit 32379a1573
50 changed files with 13636 additions and 736 deletions
+424
View File
@@ -0,0 +1,424 @@
from __future__ import annotations
import json
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import MagicMock, patch
from cognitive_agent.application.model_plan_adapter import ModelPlanAdapter
from cognitive_agent.application.native_can_intent import merge_native_can_intent
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.application.self_extension import (
SelfExtensionService,
SimulationGateway,
)
HARDWARE_DISABLED_ENV = {
"COGNITIVE_AGENT_CH1_HIL_ENABLED": "false",
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "false",
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "false",
}
def _empty_intent() -> dict:
return {
"actions": [],
"domain_actions": [],
"native_can_actions": [],
"deferred_requests": [],
"model_requests": [],
"preset_actions": [],
"preset_events": [],
"ambiguities": [],
"detected_domains": [],
"decision_summary": [],
"confidence": 0.0,
}
class Ch1DefaultTargetTests(unittest.TestCase):
def setUp(self) -> None:
self.environment = patch.dict(os.environ, HARDWARE_DISABLED_ENV, clear=False)
self.environment.start()
self.addCleanup(self.environment.stop)
self.temporary_directory = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary_directory.cleanup)
self.service = SeatDemoService(REPOSITORY_ROOT)
self.service.artifact_root = Path(self.temporary_directory.name)
def test_local_requests_use_driver_console_defaults_and_one_ch1_operation(self) -> None:
cases = (
(
"车窗开启一半",
"native_window",
"window.set_position",
"front_left",
{"position_pct": 50},
),
(
"打开车窗",
"native_window",
"window.set_motion",
"front_left",
{"motion": "open"},
),
(
"空调调到21度",
"native_climate",
"climate.set_temperature",
"driver",
{"temperature_c": 21},
),
(
"把风口左右30%上下40%",
"native_outlet",
"outlet.set_position_percent",
"driver_left",
{"horizontal_pct": 30, "vertical_pct": 40},
),
(
"打开座椅加热",
"native_seat",
"seat.set_driver_heating_native",
"driver",
{"state": "on", "level": 2},
),
(
"打开座椅通风",
"native_seat",
"seat.set_driver_ventilation_native",
"driver",
{"state": "on", "level": 2},
),
)
for index, (utterance, config_key, capability, target, parameters) in enumerate(
cases
):
with self.subTest(utterance=utterance):
result = self.service.process(
utterance,
provider="local",
conversation_id=f"conv-ch1-default-local-{index}",
execution_mode="dry_run",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(set(result["function_configs"]), {config_key})
self.assertNotIn("seat", result["function_configs"])
operations = result["function_configs"][config_key]["spec"][
"operations"
]
self.assertEqual(len(operations), 1)
self.assertEqual(operations[0]["capability"], capability)
self.assertEqual(operations[0]["target"]["zone"], target)
self.assertEqual(operations[0]["parameters"], parameters)
commands = result["command_plans"][config_key]["spec"]["commands"]
self.assertEqual(len(commands), 1)
self.assertEqual(commands[0]["bus"], "body_can")
summaries = " ".join(result["decision_summary"])
self.assertNotIn("副驾座椅功能单域", summaries)
self.assertNotIn("目标:副驾座椅", summaries)
def test_explicit_vehicle_targets_take_priority_over_defaults(self) -> None:
cases = (
("右前车窗开启一半", "native_window", "front_right", {"position_pct": 50}),
("副驾空调调到21度", "native_climate", "passenger", {"temperature_c": 21}),
)
for index, (utterance, config_key, target, parameters) in enumerate(cases):
with self.subTest(utterance=utterance):
result = self.service.process(
utterance,
provider="local",
conversation_id=f"conv-ch1-explicit-target-{index}",
)
operation = result["function_configs"][config_key]["spec"][
"operations"
][0]
self.assertEqual(operation["target"]["zone"], target)
self.assertEqual(operation["parameters"], parameters)
passenger = self.service.process(
"打开副驾驶座椅加热",
provider="local",
conversation_id="conv-ch1-explicit-passenger-seat",
)
self.assertEqual(set(passenger["function_configs"]), {"seat"})
self.assertNotIn("native_seat", passenger["function_configs"])
self.assertEqual(
passenger["function_configs"]["seat"]["spec"]["operations"][0][
"target"
]["zone"],
"front_right",
)
def test_group_or_non_driver_seat_scope_is_not_treated_as_unscoped(self) -> None:
for index, utterance in enumerate(
("打开前排座椅加热", "打开后排座椅加热", "打开所有座椅加热")
):
with self.subTest(utterance=utterance):
result = self.service.process(
utterance,
provider="local",
conversation_id=f"conv-ch1-non-driver-scope-{index}",
)
self.assertEqual(result["status"], "needs_confirmation")
self.assertEqual(result["function_configs"], {})
self.assertNotIn("native_seat", result["command_plans"])
def test_unresolved_unscoped_seat_request_never_falls_back_to_passenger_ch0(self) -> None:
parsed = _empty_intent()
parsed["actions"] = [
{
"action_type": "thermal",
"target": "passenger",
"function": "heating",
"state": "on",
}
]
parsed["model_requests"] = [
{
"domain": "seat",
"capability": "seat.set_heating",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "state", "value": "on"}],
"reason": "model guessed passenger",
}
]
result = merge_native_can_intent(parsed, "座椅调舒服一点")
self.assertFalse(result["actions"])
self.assertFalse(result["native_can_actions"])
self.assertFalse(result["model_requests"])
self.assertTrue(
any("不会转入副驾 CH0" in item["question"] for item in result["ambiguities"])
)
def test_unverified_driver_absolute_motion_is_not_degraded_to_a_timed_pulse(self) -> None:
for index, utterance in enumerate(
("座椅前后移动到50%", "靠背向后移动70度", "坐盆抬到最高")
):
with self.subTest(utterance=utterance):
result = self.service.process(
utterance,
provider="local",
conversation_id=f"conv-ch1-unsupported-absolute-{index}",
)
self.assertEqual(result["status"], "needs_confirmation")
self.assertEqual(result["function_configs"], {})
timed = self.service.process(
"座椅向前移动2秒",
provider="local",
conversation_id="conv-ch1-verified-timed-axis",
)
self.assertEqual(timed["status"], "succeeded")
operation = timed["function_configs"]["native_seat"]["spec"][
"operations"
][0]
self.assertEqual(operation["capability"], "seat.set_driver_axis_native")
self.assertEqual(
operation["parameters"],
{"axis": "horizontal", "direction": "forward", "duration_ms": 2000},
)
def test_model_adapter_overrides_only_implicit_targets_with_ch1_defaults(self) -> None:
window = ModelPlanAdapter._apply_ch1_default_target(
{
"domain": "window",
"capability": "window.set_position",
"target_scope": "all",
},
"车窗开启一半",
)
climate = ModelPlanAdapter._apply_ch1_default_target(
{
"domain": "climate",
"capability": "climate.set_temperature",
"target_scope": "vehicle",
},
"空调调到21度",
)
seat = ModelPlanAdapter._apply_ch1_default_target(
{
"domain": "seat",
"capability": "seat.set_heating",
"target_scope": "passenger",
},
"打开座椅加热",
)
explicit_passenger = ModelPlanAdapter._apply_ch1_default_target(
{
"domain": "climate",
"capability": "climate.set_temperature",
"target_scope": "passenger",
},
"副驾空调调到21度",
)
self.assertEqual(window["target_scope"], "front_left")
self.assertEqual(climate["target_scope"], "driver")
self.assertEqual(seat["capability"], "seat.set_driver_heating_native")
self.assertEqual(seat["target_scope"], "driver")
self.assertEqual(explicit_passenger["target_scope"], "passenger")
def test_gpt_provider_is_preserved_while_wrong_model_targets_are_replaced(self) -> None:
cases = (
(
"车窗开启一半",
{
"domain": "window",
"capability": "window.set_position",
"target_scope": "all",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 100}],
"reason": "model chose every window",
},
"native_window",
"front_left",
{"position_pct": 50},
),
(
"空调调到21度",
{
"domain": "climate",
"capability": "climate.set_temperature",
"target_scope": "vehicle",
"operation": "set",
"parameters": [{"name": "temperature_c", "value": 21}],
"reason": "model chose the whole vehicle",
},
"native_climate",
"driver",
{"temperature_c": 21},
),
(
"打开座椅加热",
{
"domain": "seat",
"capability": "seat.set_heating",
"target_scope": "passenger",
"operation": "set",
"parameters": [
{"name": "state", "value": "on"},
{"name": "level", "value": 3},
],
"reason": "model guessed the legacy passenger seat",
},
"native_seat",
"driver",
{"state": "on", "level": 2},
),
)
self.service.openai.api_key = "offline-test-key"
self.service.openai.model = "gpt-5.6-sol"
for index, (utterance, request, config_key, target, parameters) in enumerate(
cases
):
model_output = {
"detected_domains": [request["domain"]],
"requests": [request],
"ambiguities": [],
"decision_summary": ["GPT model output"],
"confidence": 0.9,
}
with self.subTest(utterance=utterance):
with patch.object(
self.service.openai, "interpret", return_value=model_output
) as interpret:
result = self.service.process(
utterance,
provider="openai",
conversation_id=f"conv-ch1-default-openai-{index}",
)
interpret.assert_called_once()
self.assertEqual(result["provider_used"], "openai:gpt-5.6-sol")
self.assertEqual(result["status"], "succeeded")
self.assertEqual(set(result["function_configs"]), {config_key})
self.assertNotIn("seat", result["function_configs"])
operation = result["function_configs"][config_key]["spec"][
"operations"
][0]
self.assertEqual(operation["target"]["zone"], target)
self.assertEqual(operation["parameters"], parameters)
self.assertEqual(
len(result["command_plans"][config_key]["spec"]["commands"]),
1,
)
def test_self_extension_vehicle_endpoint_uses_the_same_default_contract(self) -> None:
extension = SelfExtensionService(SimulationGateway())
extension.vehicle_provider = "local"
extension.vehicle_execution_mode = "dry_run"
extension.vehicle_id = "denza_n7"
cases = (
("车窗开启一半", "window", "front_left", {"position_pct": 50}),
("空调调到21度", "climate", "driver", {"temperature_c": 21}),
("打开座椅加热", "seat", "driver", {"state": "on", "level": 2}),
)
for index, (command, domain, target, parameters) in enumerate(cases):
with self.subTest(command=command):
result = extension.execute_vehicle(
{
"command": command,
"conversation_id": f"conv-extension-ch1-default-{index}",
},
self.service,
hardware_access_bypass=True,
)
self.assertTrue(result["success"])
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["provider_used"], "local_rules")
self.assertEqual(len(result["operation_results"]), 1)
operation = result["operation_results"][0]
self.assertEqual(operation["domain"], domain)
self.assertEqual(operation["telemetry"]["target"], target)
self.assertEqual(
operation["telemetry"]["requested_parameters"], parameters
)
def test_self_extension_gpt_prompt_limits_confirmation_to_toy_peripherals(self) -> None:
response_plan = {
"summary": "ok",
"needs_confirmation": False,
"question": "",
"steps": [],
}
response = MagicMock()
response.__enter__.return_value = response
response.read.return_value = json.dumps(
{"output_text": json.dumps(response_plan, ensure_ascii=False)},
ensure_ascii=False,
).encode("utf-8")
with patch.dict(os.environ, {"OPENAI_STREAM": "false"}, clear=False):
extension = SelfExtensionService(SimulationGateway())
with patch(
"cognitive_agent.application.self_extension.urlopen",
return_value=response,
) as open_request:
extension._request_gpt_plan(
"车窗开启一半,空调调到21度",
[],
[{"label": "车窗"}, {"label": "空调"}],
"offline-test-key",
)
request_payload = json.loads(open_request.call_args.args[0].data.decode("utf-8"))
prompt = request_payload["input"][0]["content"][0]["text"]
self.assertIn("多台可匹配的外设玩具", prompt)
self.assertIn("此追问规则不适用于", prompt)
self.assertIn("window front-left position 50", prompt)
self.assertIn("ac temp driver 21", prompt)
self.assertIn("seat driver", prompt)
self.assertIn("绝不能转入副驾/CH0", prompt)
if __name__ == "__main__":
unittest.main()
+96
View File
@@ -0,0 +1,96 @@
from __future__ import annotations
from contextlib import nullcontext
import os
from types import SimpleNamespace
import unittest
from unittest.mock import Mock, patch
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.infrastructure.can.hardware import (
HardwareSafetyError,
VehicleCanSettings,
)
class Ch1VehicleSafetyTests(unittest.TestCase):
def settings(self, channel: int) -> VehicleCanSettings:
environment = {
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "true",
"COGNITIVE_AGENT_VEHICLE_CAN_CHANNEL": str(channel),
"COGNITIVE_AGENT_VEHICLE_CAN_REQUIRED_CHANNEL": "1",
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED": "true",
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN": "approved",
}
with patch.dict(os.environ, environment, clear=False):
return VehicleCanSettings.from_env(REPOSITORY_ROOT)
def test_ch1_is_the_only_allowed_body_can_channel(self) -> None:
settings = self.settings(1)
self.assertTrue(settings.channel_is_allowed)
self.assertEqual(settings.required_channel, 1)
self.assertTrue(settings.configured)
settings.authorize("approved")
info = settings.public_info()
self.assertEqual(info["target_policy"], "ch1_body_can_driver_only")
self.assertTrue(info["channel_is_allowed"])
def test_ch0_is_rejected_before_adapter_open(self) -> None:
settings = self.settings(0)
self.assertFalse(settings.channel_is_allowed)
self.assertFalse(settings.configured)
with self.assertRaisesRegex(HardwareSafetyError, "CH1.*CH0 is forbidden"):
settings.authorize("approved")
def test_policy_cannot_be_redirected_to_ch0(self) -> None:
with patch.dict(
os.environ,
{
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "true",
"COGNITIVE_AGENT_VEHICLE_CAN_CHANNEL": "0",
"COGNITIVE_AGENT_VEHICLE_CAN_REQUIRED_CHANNEL": "0",
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED": "false",
},
clear=False,
):
settings = VehicleCanSettings.from_env(REPOSITORY_ROOT)
self.assertFalse(settings.channel_is_allowed)
with self.assertRaises(HardwareSafetyError):
settings.authorize(None)
def test_driver_only_ch1_uses_deployment_authority_without_browser_lease(self) -> None:
service = object.__new__(SeatDemoService)
service.hardware_settings = SimpleNamespace(enabled=False)
service.vehicle_can_settings = SimpleNamespace(
configured=True,
channel_is_allowed=True,
channel=1,
)
service.hardware_execution = Mock()
service.hardware_execution.reserve.return_value = nullcontext()
service.hardware_access = Mock()
service._process_once = Mock(return_value={"status": "succeeded"})
result = service.process(
"ac off",
execution_mode="hardware",
hardware_access_bypass=False,
)
self.assertEqual(result["status"], "succeeded")
self.assertTrue(service.driver_only_ch1_execution_active)
service.hardware_access.ensure_authorized.assert_not_called()
def test_browser_lease_remains_required_if_legacy_seat_path_is_enabled(self) -> None:
service = object.__new__(SeatDemoService)
service.hardware_settings = SimpleNamespace(enabled=True)
service.vehicle_can_settings = SimpleNamespace(
configured=True,
channel_is_allowed=True,
channel=1,
)
self.assertFalse(service.driver_only_ch1_execution_active)
if __name__ == "__main__":
unittest.main()
+529
View File
@@ -0,0 +1,529 @@
from __future__ import annotations
import json
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",
},
)
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",
),
}
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},
),
}
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_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()
@@ -0,0 +1,291 @@
from __future__ import annotations
import json
from pathlib import Path
import shutil
import subprocess
import unittest
from cognitive_agent.application.conversation_state import ConversationStateStore
REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
APP_SOURCE_PATH = REPOSITORY_ROOT / "apps" / "web-console" / "app.js"
EXTENSION_SOURCE_PATH = (
REPOSITORY_ROOT / "apps" / "web-console" / "self-extension.js"
)
def function_source(source: str, signature: str, next_signature: str) -> str:
start = source.index(signature)
end = source.index(next_signature, start)
return source[start:end]
class FrontendVehicleStateSyncContractTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.app_source = APP_SOURCE_PATH.read_text(encoding="utf-8")
cls.extension_source = EXTENSION_SOURCE_PATH.read_text(encoding="utf-8")
def test_both_modules_use_the_same_browser_conversation(self) -> None:
key = "cognitive-agent.conversation-id"
self.assertIn(
f'sessionStorage.getItem("{key}")',
self.app_source,
)
self.assertIn(
f'sessionStorage.setItem("{key}", conversationId)',
self.app_source,
)
self.assertIn(
f'const CONVERSATION_KEY = "{key}"',
self.extension_source,
)
self.assertIn(
"body: JSON.stringify({ command, conversation_id: sharedConversationId() })",
self.extension_source,
)
def test_lan_http_origin_does_not_require_crypto_random_uuid(self) -> None:
id_source = function_source(
self.app_source,
"function createOpaqueId(prefix) {",
"\nfunction createConversationId",
)
self.assertIn("globalThis.crypto?.randomUUID", id_source)
self.assertIn("globalThis.crypto?.getRandomValues", id_source)
self.assertIn("Math.random()", id_source)
self.assertIn('return createOpaqueId("conv")', self.app_source)
self.assertIn('createOpaqueId("hwc")', self.app_source)
def test_extension_accepts_only_explicit_physical_ch1_state(self) -> None:
authority_source = function_source(
self.extension_source,
" function isAuthoritativeVehicleStatePayload(payload) {",
"\n function renderAuthoritativeVehicleState",
)
self.assertIn(
'payload.conversation_id !== sharedConversationId()', authority_source
)
self.assertIn(
"containsAuthoritativePhysicalCh1State(payload.actuator_state)",
authority_source,
)
self.assertIn(
"containsAuthoritativePhysicalCh1State(payload.domain_state)",
authority_source,
)
self.assertNotIn("physical_feedback_confirmed", authority_source)
summary_source = function_source(
self.extension_source,
" function renderAuthoritativeVehicleState(payload) {",
"\n function setMode",
)
self.assertNotIn(
"items.length === 1 && payload.physical_feedback_confirmed",
summary_source,
)
for state_path in (
"ventilation?.feedback_source",
"heating?.feedback_source",
"climate?.feedback_source",
"roof?.feedback_source",
"frontLeftWindow?.feedback_source",
):
self.assertIn(f'{state_path} === "physical_can_ch1"', summary_source)
def test_orchestration_renders_only_the_physical_ch1_patch(self) -> None:
apply_source = function_source(
self.app_source,
"function applyAuthoritativeVehicleState(payload) {",
'\nwindow.addEventListener("cognitive-agent:extension-vehicle-result"',
)
self.assertIn(
"const actuatorPatch = physicalCh1StatePatch(payload.actuator_state)",
apply_source,
)
self.assertIn(
"const domainPatch = physicalCh1StatePatch(payload.domain_state)",
apply_source,
)
self.assertIn("renderSeatState(actuatorPatch)", apply_source)
self.assertIn("renderDomainState(domainPatch)", apply_source)
self.assertIn("if (actuatorPatch?.driver)", apply_source)
self.assertNotIn("renderSeatState(payload.actuator_state)", apply_source)
self.assertNotIn("renderDomainState(payload.domain_state)", apply_source)
render_source = function_source(
self.app_source,
"function renderResult(result) {",
"\nasync function submitRequest",
)
ch1_branch = function_source(
render_source,
" if (ch1HardwareResult) {",
" updateTwinExplicitAngles",
)
self.assertIn("applyAuthoritativeVehicleState(result)", ch1_branch)
self.assertIn("} else {", ch1_branch)
self.assertIn("renderSeatState(result.actuator_state)", ch1_branch)
self.assertIn("renderDomainState(result.domain_state)", ch1_branch)
def test_physical_patch_does_not_promote_unmarked_sibling_domains(self) -> None:
node = shutil.which("node")
if node is None:
self.skipTest("Node.js is required to execute the frontend patch helper")
patch_source = function_source(
self.app_source,
"function physicalCh1StatePatch(value) {",
"\nfunction isAuthoritativeVehicleStatePayload",
)
state = {
"climate": {
"feedback_source": "physical_can_ch1",
"power": "on",
},
"roof": {"shade_position_pct": 100},
"window": {
"positions": {"front_left": {"position_pct": 50}}
},
}
script = (
f"{patch_source}\n"
f"const input = {json.dumps(state)};\n"
"process.stdout.write(JSON.stringify(physicalCh1StatePatch(input)));"
)
completed = subprocess.run(
[node, "-e", script],
check=True,
capture_output=True,
text=True,
)
self.assertEqual(
json.loads(completed.stdout),
{"climate": state["climate"]},
)
def test_ch1_mode_never_polls_the_legacy_ch0_seat_endpoint(self) -> None:
sync_source = function_source(
self.app_source,
"async function synchronizePhysicalSeatState() {",
"\nasync function loadAuthStatus()",
)
guard = sync_source.index(
'target_policy === "ch1_body_can_driver_only"'
)
legacy_request = sync_source.index("/api/hardware/seat-state")
self.assertLess(guard, legacy_request)
self.assertIn("return false", sync_source[guard:legacy_request])
def test_vehicle_selection_cannot_replay_unfiltered_state_in_ch1_mode(self) -> None:
select_source = function_source(
self.app_source,
"function selectVehicle(vehicleId, { silent = false } = {}) {",
"\nfunction populateVehicleSelector",
)
driver_branch = select_source.index("if (isDriverOnlyCh1Runtime())")
filtered_snapshot = select_source.index(
"state.vehicleStateSnapshot?.actuatorState",
driver_branch,
)
unfiltered_result = select_source.index("state.result?.actuator_state")
self.assertLess(driver_branch, filtered_snapshot)
self.assertLess(filtered_snapshot, unfiltered_result)
self.assertIn("} else if (state.result?.actuator_state)", select_source)
def test_driver_only_ch1_has_no_hidden_browser_lease_gate(self) -> None:
submit_source = function_source(
self.app_source,
"async function submitRequest(event) {",
"\nasync function copyArtifact",
)
self.assertIn("const driverOnlyCh1 = isDriverOnlyCh1Runtime()", submit_source)
self.assertIn("&& !driverOnlyCh1", submit_source)
self.assertIn(
'state.executionMode === "hardware" && !driverOnlyCh1',
submit_source,
)
startup_source = self.app_source[
self.app_source.index('document.addEventListener("DOMContentLoaded"') :
]
self.assertIn("if (!isDriverOnlyCh1Runtime())", startup_source)
self.assertIn("loadHardwareAccessStatus", startup_source)
def test_state_results_flow_in_both_directions(self) -> None:
self.assertIn(
'window.addEventListener("cognitive-agent:extension-vehicle-result"',
self.app_source,
)
self.assertIn(
'window.addEventListener("cognitive-agent:result"',
self.extension_source,
)
class PhysicalMarkerInvalidationContractTests(unittest.TestCase):
def test_logical_door_and_window_actions_replace_stale_physical_roots(self) -> None:
store = ConversationStateStore()
conversation_id = "conv-frontend-marker-invalidation"
store.synchronize_physical_ch1_feedback(
conversation_id,
[
{
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x3B6",
"feedback_frame_id": "0x3B6",
"valid": True,
"verification": {
"authoritative": True,
"channel": 1,
"simulated": False,
"feedback_required": True,
"feedback_observed": True,
"feedback_match": True,
"frame_id": "0x3B6",
},
"domain_state": {
"door": {"locks": {"rear_left": {"state": "lock"}}},
"window": {
"positions": {
"front_left": {
"position_pct": 20,
"closed": False,
}
}
},
},
}
],
)
physical = store.snapshot(conversation_id)["domain_devices"]
self.assertEqual(physical["door"]["feedback_source"], "physical_can_ch1")
self.assertEqual(physical["window"]["feedback_source"], "physical_can_ch1")
updated = store.apply_successful_actions(
conversation_id,
"logical projection must not look physical",
[
{
"domain": "door",
"capability": "door.set_rear_lock",
"target": "rear",
"parameters": {"state": "unlock"},
},
{
"domain": "window",
"capability": "window.set_position",
"target": "front_left",
"parameters": {"position_pct": 60},
},
],
[],
)["domain_devices"]
self.assertEqual(updated["door"]["feedback_source"], "virtual")
self.assertEqual(updated["window"]["feedback_source"], "virtual")
if __name__ == "__main__":
unittest.main()
+154
View File
@@ -0,0 +1,154 @@
from __future__ import annotations
from copy import deepcopy
import unittest
from cognitive_agent.application.native_can_feedback import (
authoritative_physical_ch1_feedback_state,
decode_feedback,
feedback_matches,
merge_state,
)
from cognitive_agent.infrastructure.can.gateway import CanFrame
class NativeCanFeedbackTests(unittest.TestCase):
def frame(self, frame_id: int, data_hex: str) -> CanFrame:
return CanFrame(frame_id, bytes.fromhex(data_hex), channel=1, is_fd=True)
def test_passive_ch1_samples_decode_stationary_vehicle(self) -> None:
gear = decode_feedback(self.frame(0x342, "00001fb1c1fff778"))
speed = decode_feedback(self.frame(0x4EB, "0700f00001516766"))
self.assertEqual(gear["vehicle_safety"]["gear"], "P")
self.assertEqual(speed["vehicle_safety"]["speed_kmh"], 0)
self.assertEqual(gear["source"], "physical_can_ch1")
self.assertEqual(gear["channel"], 1)
def test_climate_feedback_is_matched_by_signal_value(self) -> None:
feedback = self.frame(0x404, "4369116c1818a2dd")
decoded = decode_feedback(feedback)["domain_state"]["climate"]
self.assertEqual(decoded["power"], "on")
self.assertEqual(decoded["zones"]["driver"]["temperature_c"], 24)
self.assertTrue(feedback_matches("climate_power:on", feedback))
self.assertTrue(feedback_matches("climate_temperature:driver:24", feedback))
self.assertFalse(feedback_matches("climate_temperature:driver:25", feedback))
def test_window_position_match_uses_authoritative_0x388(self) -> None:
feedback = self.frame(0x388, "0000320000005500")
self.assertTrue(feedback_matches("window_position:front_left:50", feedback))
self.assertFalse(feedback_matches("window_position:front_left:80", feedback))
def test_light_feedback_requires_target_value_not_only_frame_id(self) -> None:
passive_feedback = self.frame(0x42E, "11140a03880200c6")
self.assertEqual(
decode_feedback(passive_feedback)["domain_state"]["interior_light"]["power"],
"unknown",
)
feedback = self.frame(0x42E, "11140a03200200c6")
decoded = decode_feedback(feedback)["domain_state"]
self.assertEqual(decoded["interior_light"]["power"], "off")
self.assertEqual(decoded["ambient_light"]["power"], "off")
self.assertTrue(feedback_matches("indoor_light:off", feedback))
self.assertFalse(feedback_matches("indoor_light:on", feedback))
self.assertTrue(feedback_matches("ambient_power:off", feedback))
def test_driver_seat_feedback_uses_0x4fa_not_0x42a(self) -> None:
passive_feedback = self.frame(0x4FA, "0980aa0000810000")
passive_driver = decode_feedback(passive_feedback)["actuator_state"]["driver"]
self.assertEqual(passive_driver["heating"], {"state": "off", "level": 0})
feedback = self.frame(0x4FA, "1180aa0000810000")
driver = decode_feedback(feedback)["actuator_state"]["driver"]
self.assertEqual(driver["ventilation"], {"state": "off", "level": 0})
self.assertEqual(driver["heating"], {"state": "on", "level": 1})
self.assertTrue(feedback_matches("driver_seat_thermal:heating:low", feedback))
self.assertFalse(feedback_matches("driver_seat_thermal:ventilation:high", feedback))
def test_driver_position_feedback_never_claims_absolute_position(self) -> None:
decoded = decode_feedback(
self.frame(0x494, "151515058c28203c")
)["actuator_state"]["driver"]["position_feedback"]
self.assertFalse(decoded["absolute_position_available"])
self.assertEqual(
set(decoded["initialized"]),
{"horizontal", "backrest", "height", "basin"},
)
def test_recursive_state_merge_preserves_other_devices(self) -> None:
target = {"driver": {"heating": {"state": "off"}}, "passenger": {"safe": True}}
update = {"driver": {"heating": {"state": "on", "level": 1}}}
merge_state(target, update)
self.assertEqual(target["driver"]["heating"]["state"], "on")
self.assertEqual(target["passenger"], {"safe": True})
def test_authoritative_state_requires_one_complete_consistent_ch1_frame(
self,
) -> None:
operation = {
"status": "succeeded",
"telemetry": {
"channel": 1,
"simulated": False,
"feedback_required": True,
"feedback_observed": True,
"feedback_match": True,
"feedback_frame": {
"frame_id": "0x404",
"payload_size": 8,
"data_hex": "5B 65 35 6C 18 18 A2 5D",
"timestamp": 12.5,
"channel": 1,
},
"feedback_state": {
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x404",
"feedback_frame_id": "0x404",
"valid": True,
"domain_state": {"climate": {"power": "on"}},
},
},
}
state = authoritative_physical_ch1_feedback_state(operation)
self.assertIsNotNone(state)
self.assertTrue(state["verification"]["authoritative"])
self.assertEqual(state["verification"]["frame_id"], "0x404")
for label, mutate in (
(
"wrong frame channel",
lambda item: item["telemetry"]["feedback_frame"].update(
{"channel": 0}
),
),
(
"short payload",
lambda item: item["telemetry"]["feedback_frame"].update(
{"payload_size": 7}
),
),
(
"mismatched decoded id",
lambda item: item["telemetry"]["feedback_state"].update(
{"feedback_frame_id": "0x4FA"}
),
),
(
"empty decoded patch",
lambda item: item["telemetry"]["feedback_state"].pop(
"domain_state"
),
),
):
invalid = deepcopy(operation)
mutate(invalid)
with self.subTest(label=label):
self.assertIsNone(
authoritative_physical_ch1_feedback_state(invalid)
)
if __name__ == "__main__":
unittest.main()
+79
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import os
import copy
import unittest
from pathlib import Path
from unittest.mock import patch
@@ -140,6 +141,62 @@ class NativeCanIntegrationTests(unittest.TestCase):
).execute(self.runtime, plan, config)
self.assertEqual(report["spec"]["status"], "failed")
def test_executor_ignores_stale_wrong_value_until_target_feedback_arrives(self) -> None:
config, plan = self._compile(
"climate",
[
{
"domain": "climate",
"capability": "climate.set_power",
"target": "vehicle",
"operation": "set",
"parameters": {"state": "on"},
}
],
)
off = CanFrame(0x404, bytes((0, 2, 0, 0, 0, 0, 0, 0)), channel=1)
on = CanFrame(0x404, bytes((0, 1, 0, 0, 0, 0, 0, 0)), channel=1)
bus = _FakeBus([off, on])
with patch("cognitive_agent.application.native_can_runtime.time.sleep"):
report = NativeCanExecutor(lambda: bus, feedback_timeout_ms=10).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_frame"]["data_hex"], "00 01 00 00 00 00 00 00")
self.assertEqual(telemetry["channel"], 1)
def test_executor_refuses_to_send_without_value_match_contract(self) -> None:
config, plan = self._compile(
"climate",
[
{
"domain": "climate",
"capability": "climate.set_power",
"target": "vehicle",
"operation": "set",
"parameters": {"state": "on"},
}
],
)
unsafe_plan = copy.deepcopy(plan)
unsafe_plan["spec"]["commands"][0]["signals"]["expected_feedback"] = ""
bus = _FakeBus()
report = NativeCanExecutor(lambda: bus, feedback_timeout_ms=1).execute(
self.runtime, unsafe_plan, config
)
self.assertEqual(report["spec"]["status"], "failed")
self.assertFalse(bus.sent)
self.assertIn(
"target-value match contract",
report["spec"]["results"][0]["message"],
)
def test_one_sentence_builds_seat_and_vehicle_channel_plans(self) -> None:
service = SeatDemoService(REPOSITORY_ROOT)
result = service.process(
@@ -154,6 +211,28 @@ class NativeCanIntegrationTests(unittest.TestCase):
self.assertTrue(all(item["bus"] == "body_can" for item in body_commands))
self.assertFalse(result["deferred_requests"])
def test_retired_vehicle_lock_intent_returns_explicit_unavailable_result(self) -> None:
service = SeatDemoService(REPOSITORY_ROOT)
for index, command in enumerate(("door vehicle lock", "door vehicle unlock")):
with self.subTest(command=command):
result = service.process(
command,
provider="local",
conversation_id=f"conv-retired-door-lock-{index}",
execution_mode="dry_run",
)
self.assertEqual(result["status"], "capability_unavailable")
self.assertEqual(result["function_configs"], {})
self.assertEqual(result["command_plans"], {})
self.assertEqual(
result["deferred_requests"][0]["rejection_code"],
"CH1_DOOR_LOCK_CONTROL_UNSUPPORTED",
)
self.assertEqual(
result["deferred_requests"][0]["execution_backend"],
"unsupported",
)
def test_vehicle_timing_contract_matches_requested_60mhz_values(self) -> None:
settings = VehicleCanSettings.from_env(Path(REPOSITORY_ROOT))
self.assertTrue(settings.timing_is_verified)
+230
View File
@@ -0,0 +1,230 @@
from __future__ import annotations
import unittest
from cognitive_agent.application.conversation_state import ConversationStateStore
class NativeStateSyncTests(unittest.TestCase):
def setUp(self) -> None:
self.store = ConversationStateStore()
def apply(self, action: dict) -> dict:
return self.store.apply_successful_actions(
"conv-native-sync",
"test",
[{**action, "feedback_source": "physical_can_ch1"}],
[],
)
def test_driver_seat_native_feedback_never_updates_passenger(self) -> None:
state = self.apply(
{
"domain": "seat",
"capability": "seat.set_driver_heating_native",
"target": "driver",
"parameters": {"state": "low"},
}
)
self.assertEqual(state["devices"]["driver"]["heating"]["state"], "on")
self.assertEqual(state["devices"]["driver"]["heating"]["level"], 1)
self.assertEqual(
state["devices"]["driver"]["heating"]["feedback_source"],
"physical_can_ch1",
)
self.assertEqual(state["devices"]["passenger"]["heating"]["state"], "off")
def test_window_feedback_updates_frontend_domain_state(self) -> None:
state = self.apply(
{
"domain": "window",
"capability": "window.set_position",
"target": "front_left",
"parameters": {"position_pct": 25},
}
)
window = state["domain_devices"]["window"]["positions"]["front_left"]
self.assertEqual(window["position_pct"], 25)
self.assertFalse(window["closed"])
self.assertEqual(window["feedback_source"], "physical_can_ch1")
def test_rear_lock_updates_only_rear_locks(self) -> None:
state = self.apply(
{
"domain": "door",
"capability": "door.set_rear_lock",
"target": "rear",
"parameters": {"state": "lock"},
}
)
locks = state["domain_devices"]["door"]["locks"]
self.assertEqual(locks["rear_left"]["state"], "lock")
self.assertEqual(locks["rear_right"]["state"], "lock")
self.assertEqual(locks["front_left"]["state"], "unknown")
def test_vehicle_lock_projection_updates_logical_and_all_door_states(self) -> None:
state = self.apply(
{
"domain": "door",
"capability": "door.set_vehicle_lock",
"target": "vehicle",
"parameters": {"state": "lock"},
}
)
door = state["domain_devices"]["door"]
self.assertEqual(door["vehicle_lock"]["state"], "lock")
self.assertTrue(
all(item["state"] == "lock" for item in door["locks"].values())
)
def test_authoritative_ch1_feedback_overrides_requested_ui_projection(self) -> None:
state = self.store.synchronize_physical_ch1_feedback(
"conv-native-sync",
[
{
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x404",
"feedback_frame_id": "0x404",
"valid": True,
"verification": {
"authoritative": True,
"channel": 1,
"simulated": False,
"feedback_required": True,
"feedback_observed": True,
"feedback_match": True,
"frame_id": "0x404",
"payload_size": 8,
},
"actuator_state": {
"driver": {
"ventilation": {"state": "on", "level": 3},
"massage": {"mode": "relax", "strength": "weak"},
}
},
"domain_state": {
"climate": {
"power": "on",
"ac": "on",
"control_mode": "manual",
"cycle_mode": "internal",
"fan_level": 3,
"zones": {
"driver": {
"temperature_c": 24,
"outlet_mode": "defrost",
}
},
},
"roof": {
"shade_position_pct": 2,
"motion_state": "stopped",
},
"ambient_light": {
"power": "on",
"front_brightness_level": 3,
"rear_brightness_level": 3,
"front_color_index": 32,
"rear_color_index": 32,
},
"door": {
"locks": {
"rear_left": {"state": "lock"},
"rear_right": {"state": "lock"},
}
},
"window": {
"positions": {
"front_left": {
"position_pct": 9,
"closed": False,
}
}
},
},
}
],
)
self.assertEqual(state["devices"]["driver"]["ventilation"]["level"], 3)
self.assertEqual(
state["devices"]["driver"]["ventilation"]["feedback_source"],
"physical_can_ch1",
)
climate = state["domain_devices"]["climate"]
self.assertEqual(climate["zones"]["driver"]["outlet_mode"], "defrost")
self.assertEqual(climate["cycle_mode"], "internal")
self.assertEqual(climate["feedback_source"], "physical_can_ch1")
self.assertEqual(state["domain_devices"]["roof"]["shade_position_pct"], 2)
self.assertEqual(state["domain_devices"]["ambient_light"]["brightness_pct"], 100)
self.assertEqual(
state["domain_devices"]["door"]["locks"]["rear_left"]["feedback_source"],
"physical_can_ch1",
)
self.assertEqual(
state["domain_devices"]["window"]["positions"]["front_left"]["position_pct"],
9,
)
self.assertNotIn(
"feedback_source",
state["domain_devices"]["window"]["positions"]["rear_left"],
)
self.assertNotIn(
"feedback_source",
state["domain_devices"]["door"]["locks"]["front_left"],
)
def test_non_authoritative_feedback_is_ignored(self) -> None:
before = self.store.snapshot("conv-native-sync")
after = self.store.synchronize_physical_ch1_feedback(
"conv-native-sync",
[
{
"source": "physical_can_ch0",
"channel": 0,
"valid": True,
"domain_state": {"climate": {"power": "on"}},
}
],
)
self.assertEqual(after, before)
def test_unverified_or_empty_feedback_never_updates_state(self) -> None:
before = self.store.snapshot("conv-native-sync")
for feedback in (
{
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x404",
"feedback_frame_id": "0x404",
"valid": True,
"domain_state": {"climate": {"power": "on"}},
},
{
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x404",
"feedback_frame_id": "0x404",
"valid": True,
"verification": {
"authoritative": True,
"channel": 1,
"simulated": False,
"feedback_required": True,
"feedback_observed": True,
"feedback_match": True,
"frame_id": "0x404",
"payload_size": 8,
},
},
):
with self.subTest(feedback=feedback):
after = self.store.synchronize_physical_ch1_feedback(
"conv-native-sync", [feedback]
)
self.assertEqual(after, before)
if __name__ == "__main__":
unittest.main()
+675 -6
View File
@@ -1,20 +1,36 @@
from __future__ import annotations
from collections.abc import Callable
from copy import deepcopy
import json
import os
import socket
import threading
import time
import unittest
from unittest.mock import Mock, patch
from unittest.mock import MagicMock, Mock, patch
from cognitive_agent.application.self_extension import (
ExtensionIntegrationError,
GatewayError,
SelfExtensionService,
SimulationGateway,
TcpGateway,
build_gateway_command,
validate_slot,
)
class SelfExtensionTests(unittest.TestCase):
@staticmethod
def _wait_until(predicate: Callable[[], bool], timeout: float = 3.0) -> bool:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if predicate():
return True
time.sleep(0.02)
return predicate()
def test_gateway_command_contracts_and_slot_validation(self) -> None:
command = build_gateway_command("learn", {"slot": 17})
self.assertEqual(command["type"], "infrared")
@@ -59,7 +75,7 @@ class SelfExtensionTests(unittest.TestCase):
{
"kind": "vehicle",
"capability_id": "",
"command": "打开全部阅读灯",
"command": "light reading all on",
"label": "车内灯光 · 阅读灯",
},
],
@@ -82,21 +98,149 @@ class SelfExtensionTests(unittest.TestCase):
"risk": "low",
}
],
"vehicle_devices": [],
"vehicle_devices": [
{
"name": "车内灯光",
"actions": [
{
"name": "阅读灯",
"command": "light reading all on",
}
],
}
],
}
)
self.assertEqual(len(result["steps"]), 2)
self.assertEqual(result["steps"][0]["capability_id"], "cap-light-on")
self.assertEqual(result["steps"][1]["command"], "打开全部阅读灯")
self.assertEqual(result["steps"][1]["command"], "light reading all on")
def test_plan_rejects_vehicle_command_outside_current_catalog(self) -> None:
service = SelfExtensionService(SimulationGateway())
proposed = {
"summary": "unsafe target",
"needs_confirmation": False,
"question": "",
"steps": [
{
"kind": "vehicle",
"capability_id": "",
"command": "seat passenger vent high",
"label": "wrong seat",
"delay_ms": 0,
}
],
}
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False):
with patch.object(
service,
"_request_gpt_plan",
return_value=json.dumps(proposed),
):
with self.assertRaisesRegex(ExtensionIntegrationError, "CH1"):
service.plan(
{
"utterance": "打开座椅通风",
"peripherals": [],
"vehicle_devices": [
{
"name": "主驾座椅",
"actions": [
{
"name": "通风高档",
"command": "seat driver vent high",
}
],
}
],
}
)
def test_gpt_plan_honors_streaming_responses_configuration(self) -> None:
proposed = json.dumps(
{
"summary": "关闭空调",
"needs_confirmation": False,
"question": "",
"steps": [
{
"kind": "vehicle",
"capability_id": "",
"command": "ac off",
"label": "关闭空调",
}
],
},
ensure_ascii=False,
)
stream_body = "\n".join(
(
"event: response.output_text.delta",
"data: "
+ json.dumps(
{
"type": "response.output_text.delta",
"delta": proposed[:20],
},
ensure_ascii=False,
),
"event: response.output_text.done",
"data: "
+ json.dumps(
{"type": "response.output_text.done", "text": proposed},
ensure_ascii=False,
),
"data: [DONE]",
)
)
response = MagicMock()
response.__enter__.return_value = response
response.read.return_value = stream_body.encode("utf-8")
with patch.dict(os.environ, {"OPENAI_STREAM": "true"}, clear=False):
service = SelfExtensionService(SimulationGateway())
with patch(
"cognitive_agent.application.self_extension.urlopen",
return_value=response,
) as open_request:
output = service._request_gpt_plan(
"关闭空调",
[],
[
{
"name": "空调",
"actions": [{"name": "关闭空调", "command": "ac off"}],
}
],
"test-key",
)
self.assertEqual(json.loads(output)["steps"][0]["command"], "ac off")
request_payload = json.loads(
open_request.call_args.args[0].data.decode("utf-8")
)
self.assertIs(request_payload["stream"], True)
self.assertEqual(request_payload["model"], "gpt-5.6-sol")
command_schema = request_payload["text"]["format"]["schema"]["properties"][
"steps"
]["items"]["properties"]["command"]
self.assertEqual(command_schema["enum"], ["", "ac off"])
def test_vehicle_execution_reuses_the_same_orchestration_service(self) -> None:
service = SelfExtensionService(SimulationGateway())
with patch.dict(
os.environ,
{"COGNITIVE_AGENT_EXECUTION_MODE": "dry_run"},
clear=False,
):
service = SelfExtensionService(SimulationGateway())
vehicle_service = Mock()
vehicle_service.process.return_value = {
"status": "succeeded",
"provider_used": "local_rules",
"request_id": "req-extension-1",
"conversation_id": "conv-extension-shared",
"conversation_turn": 3,
"decision_summary": ["打开阅读灯"],
"validation": [],
"execution_report": {"spec": {"results": [{"status": "succeeded"}]}},
@@ -104,17 +248,542 @@ class SelfExtensionTests(unittest.TestCase):
}
result = service.execute_vehicle(
{"command": "打开全部阅读灯", "vehicle_id": "denza_n7"},
{
"command": "打开全部阅读灯",
"conversation_id": "conv-extension-shared",
"vehicle_id": "denza_n7",
},
vehicle_service,
hardware_access_bypass=True,
)
self.assertTrue(result["success"])
self.assertEqual(result["request_id"], "req-extension-1")
self.assertEqual(result["conversation_id"], "conv-extension-shared")
self.assertEqual(result["conversation_turn"], 3)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(
result["execution_report"], vehicle_service.process.return_value["execution_report"]
)
self.assertEqual(result["operation_results"], [{"status": "succeeded"}])
self.assertFalse(result["physical_feedback_required"])
self.assertFalse(result["physical_feedback_confirmed"])
vehicle_service.process.assert_called_once()
self.assertEqual(vehicle_service.process.call_args.args[0], "打开全部阅读灯")
self.assertEqual(
vehicle_service.process.call_args.args[2], "conv-extension-shared"
)
self.assertTrue(vehicle_service.process.call_args.kwargs["hardware_access_bypass"])
def test_vehicle_execution_only_marks_exact_complete_status_as_success(self) -> None:
service = SelfExtensionService(SimulationGateway())
for status in (
"validation_failed",
"needs_confirmation",
"capability_unavailable",
"partially_succeeded",
):
with self.subTest(status=status):
vehicle_service = Mock()
vehicle_service.process.return_value = {
"status": status,
"provider_used": "local_rules",
"request_id": f"req-{status}",
"decision_summary": [],
"validation": [],
"execution_report": {
"spec": {"results": [{"status": "succeeded"}]}
},
"deferred_requests": [],
}
result = service.execute_vehicle(
{"command": "打开全部阅读灯"},
vehicle_service,
hardware_access_bypass=True,
)
self.assertFalse(result["success"])
self.assertEqual(result["status"], status)
def test_vehicle_execution_rejects_failed_or_missing_operation_results(self) -> None:
service = SelfExtensionService(SimulationGateway())
for operation_results in (
[],
[{"status": "succeeded"}, {"status": "failed", "message": "timeout"}],
):
with self.subTest(operation_results=operation_results):
vehicle_service = Mock()
vehicle_service.process.return_value = {
"status": "succeeded",
"execution_report": {"spec": {"results": operation_results}},
"domain_state": {"roof": {"shade_position_pct": 31}},
"actuator_state": {"driver": {"heating": {"state": "off"}}},
}
result = service.execute_vehicle(
{"command": "打开遮阳帘"},
vehicle_service,
hardware_access_bypass=True,
)
self.assertFalse(result["success"])
self.assertEqual(
result["domain_state"], {"roof": {"shade_position_pct": 31}}
)
self.assertEqual(
result["actuator_state"],
{"driver": {"heating": {"state": "off"}}},
)
def test_hardware_vehicle_execution_requires_authoritative_feedback_per_item(
self,
) -> None:
with patch.dict(
os.environ,
{"COGNITIVE_AGENT_EXECUTION_MODE": "hardware"},
clear=False,
):
service = SelfExtensionService(SimulationGateway())
vehicle_service = Mock()
vehicle_service.process.return_value = {
"status": "succeeded",
"provider_used": "local_rules",
"request_id": "req-extension-hardware",
"execution_report": {
"spec": {
"status": "succeeded",
"physical_state": {
"source": "physical_can_ch1",
"complete": True,
},
"results": [
{
"ref": "operation-1",
"domain": "climate",
"status": "succeeded",
"telemetry": {
"channel": 1,
"simulated": False,
"feedback_required": True,
"feedback_observed": True,
"feedback_match": True,
"feedback_frame": {
"frame_id": "0x404",
"channel": 1,
"payload_size": 8,
"data_hex": "00 02 00 00 18 18 00 00",
},
"feedback_state": {
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x404",
"feedback_frame_id": "0x404",
"valid": True,
"domain_state": {"climate": {"power": "on"}},
},
},
},
{
"ref": "operation-2",
"domain": "seat",
"status": "succeeded",
"telemetry": {
"channel": 1,
"simulated": False,
"feedback_required": True,
"feedback_observed": True,
"feedback_match": True,
"feedback_frame": {
"frame_id": "0x4FA",
"channel": 1,
"payload_size": 8,
"data_hex": "02 00 00 00 00 00 00 00",
},
"feedback_state": {
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x4FA",
"feedback_frame_id": "0x4FA",
"valid": True,
"actuator_state": {
"driver": {
"ventilation": {
"state": "on",
"level": 1,
}
}
},
},
},
},
],
}
},
"domain_state": {"climate": {"power": "on"}},
"actuator_state": {"driver": {"ventilation": {"state": "on"}}},
}
confirmed = service.execute_vehicle(
{"command": "打开空调"},
vehicle_service,
hardware_access_bypass=True,
)
self.assertTrue(confirmed["success"])
self.assertTrue(confirmed["physical_feedback_required"])
self.assertTrue(confirmed["physical_feedback_confirmed"])
self.assertEqual(confirmed["physical_state"]["source"], "physical_can_ch1")
self.assertEqual(len(confirmed["physical_feedback"]), 2)
self.assertTrue(all(item["confirmed"] for item in confirmed["physical_feedback"]))
self.assertTrue(all(item["required"] for item in confirmed["physical_feedback"]))
self.assertTrue(all(item["observed"] for item in confirmed["physical_feedback"]))
self.assertTrue(all(item["match"] is True for item in confirmed["physical_feedback"]))
self.assertTrue(all(item["channel"] == 1 for item in confirmed["physical_feedback"]))
self.assertEqual(
confirmed["physical_feedback"][0]["state"]["source"],
"physical_can_ch1",
)
operation = vehicle_service.process.return_value["execution_report"]["spec"][
"results"
][1]
valid_telemetry = deepcopy(operation["telemetry"])
operation["telemetry"] = {**valid_telemetry, "feedback_match": None}
unconfirmed = service.execute_vehicle(
{"command": "打开空调"},
vehicle_service,
hardware_access_bypass=True,
)
self.assertFalse(unconfirmed["success"])
self.assertFalse(unconfirmed["physical_feedback_confirmed"])
self.assertIn("未逐项获得权威物理反馈", unconfirmed["message"])
invalid_cases: list[tuple[str, dict]] = []
for field, value in (
("channel", 0),
("simulated", True),
("feedback_required", False),
("feedback_observed", False),
("feedback_match", False),
("feedback_frame", None),
("feedback_state", None),
):
invalid_cases.append((field, {**valid_telemetry, field: value}))
wrong_frame_channel = deepcopy(valid_telemetry)
wrong_frame_channel["feedback_frame"]["channel"] = 0
invalid_cases.append(("feedback_frame.channel", wrong_frame_channel))
missing_frame_payload = deepcopy(valid_telemetry)
missing_frame_payload["feedback_frame"].pop("data_hex")
invalid_cases.append(("feedback_frame.data_hex", missing_frame_payload))
incomplete_frame_payload = deepcopy(valid_telemetry)
incomplete_frame_payload["feedback_frame"]["payload_size"] = 7
invalid_cases.append(
("feedback_frame.payload_size", incomplete_frame_payload)
)
wrong_source = deepcopy(valid_telemetry)
wrong_source["feedback_state"]["source"] = "physical_can_ch0"
invalid_cases.append(("feedback_state.source", wrong_source))
missing_decoded_state = deepcopy(valid_telemetry)
missing_decoded_state["feedback_state"].pop("actuator_state")
invalid_cases.append(("feedback_state.actuator_state", missing_decoded_state))
mismatched_frame = deepcopy(valid_telemetry)
mismatched_frame["feedback_state"]["frame_id"] = "0x404"
invalid_cases.append(("feedback_state.frame_id", mismatched_frame))
invalid_cases.append(
(
"legacy_ch0",
{
"channel": 0,
"simulated": False,
"hardware_feedback": True,
"feedback_frame": {"frame_id": "0x302"},
},
)
)
for label, insufficient_feedback in invalid_cases:
with self.subTest(insufficient_feedback=label):
operation["telemetry"] = insufficient_feedback
rejected = service.execute_vehicle(
{"command": "打开空调"},
vehicle_service,
hardware_access_bypass=True,
)
self.assertFalse(rejected["success"])
self.assertFalse(rejected["physical_feedback_confirmed"])
def test_vehicle_execution_ignores_browser_routing_overrides(self) -> None:
with patch.dict(
os.environ,
{
"COGNITIVE_AGENT_EXECUTION_MODE": "dry_run",
"COGNITIVE_AGENT_PROVIDER": "local_rules",
"COGNITIVE_AGENT_VEHICLE_ID": "denza_n7",
},
clear=False,
):
service = SelfExtensionService(SimulationGateway())
vehicle_service = Mock()
vehicle_service.process.return_value = {
"status": "succeeded",
"execution_report": {"spec": {"results": [{"status": "succeeded"}]}},
}
service.execute_vehicle(
{
"command": "seat driver vent low",
"execution_mode": "hardware",
"provider": "browser-selected-provider",
"vehicle_id": "t2g_primary_seat",
"simulation_fault": "adapter_disconnect",
"channel": "0",
},
vehicle_service,
hardware_access_bypass=True,
)
args = vehicle_service.process.call_args.args
kwargs = vehicle_service.process.call_args.kwargs
self.assertEqual(args[1], "local_rules")
self.assertEqual(args[3], "dry_run")
self.assertEqual(kwargs["vehicle_id"], "denza_n7")
self.assertEqual(kwargs["simulation_fault"], "normal")
def test_tcp_gateway_recovers_from_unavailable_address_and_tracks_peers(self) -> None:
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
probe.bind(("127.0.0.1", 0))
port = int(probe.getsockname()[1])
probe.close()
class UnavailableAddressSocket:
def setsockopt(self, *args: object) -> None:
del args
def bind(self, address: object) -> None:
del address
raise OSError(10049, "address is not available")
def close(self) -> None:
return
socket_attempts = 0
def new_server_socket() -> object:
nonlocal socket_attempts
socket_attempts += 1
if socket_attempts == 1:
return UnavailableAddressSocket()
return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clients: list[socket.socket] = []
with patch.dict(
os.environ,
{
"GSCU_TCP_LISTEN_HOST": "127.0.0.1",
"GSCU_TCP_PORT": str(port),
"GSCU_TCP_BIND_RETRY_SECONDS": "0.05",
},
):
gateway = TcpGateway()
with patch.object(gateway, "_new_server_socket", side_effect=new_server_socket):
gateway.start()
try:
self.assertTrue(
self._wait_until(lambda: gateway.status()["listening"]),
gateway.status(),
)
self.assertGreaterEqual(socket_attempts, 2)
waiting = gateway.status()
self.assertFalse(waiting["connected"])
self.assertIn("等待设备接入", waiting["message"])
waiting_health = SelfExtensionService(gateway).health()
self.assertTrue(waiting_health["listening"])
self.assertFalse(waiting_health["peer_connected"])
first = socket.create_connection(("127.0.0.1", port), timeout=1)
clients.append(first)
self.assertTrue(
self._wait_until(lambda: gateway.status()["connected"]),
gateway.status(),
)
first_peer = gateway.status()["peer"]
self.assertTrue(str(first_peer).startswith("127.0.0.1:"))
connected_health = SelfExtensionService(gateway).health()
self.assertTrue(connected_health["connected"])
self.assertEqual(connected_health["peer"], first_peer)
# Replacing an old peer must not let its reader mark the new
# GSCU1B session offline when the old socket closes.
second = socket.create_connection(("127.0.0.1", port), timeout=1)
clients.append(second)
self.assertTrue(
self._wait_until(
lambda: gateway.status()["peer"] not in {None, first_peer}
),
gateway.status(),
)
time.sleep(0.1)
self.assertTrue(gateway.status()["connected"])
second.close()
clients.remove(second)
self.assertTrue(
self._wait_until(lambda: not gateway.status()["connected"]),
gateway.status(),
)
self.assertTrue(gateway.status()["listening"])
third = socket.create_connection(("127.0.0.1", port), timeout=1)
clients.append(third)
self.assertTrue(
self._wait_until(lambda: gateway.status()["connected"]),
gateway.status(),
)
finally:
for client in clients:
client.close()
gateway.close()
stopped = gateway.status()
self.assertFalse(stopped["listening"])
self.assertFalse(stopped["connected"])
def test_tcp_gateway_recovers_after_port_conflict_is_released(self) -> None:
blocker = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
blocker.bind(("127.0.0.1", 0))
blocker.listen(1)
port = int(blocker.getsockname()[1])
with patch.dict(
os.environ,
{
"GSCU_TCP_LISTEN_HOST": "127.0.0.1",
"GSCU_TCP_PORT": str(port),
"GSCU_TCP_BIND_RETRY_SECONDS": "0.05",
},
):
gateway = TcpGateway()
gateway.start()
try:
self.assertTrue(
self._wait_until(lambda: "监听失败" in gateway.status().get("message", "")),
gateway.status(),
)
self.assertFalse(gateway.status()["listening"])
blocker.close()
self.assertTrue(
self._wait_until(lambda: gateway.status()["listening"]),
gateway.status(),
)
self.assertNotIn("失败", gateway.status()["message"])
finally:
blocker.close()
gateway.close()
def test_tcp_gateway_can_restart_immediately_after_close(self) -> None:
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
probe.bind(("127.0.0.1", 0))
port = int(probe.getsockname()[1])
probe.close()
with patch.dict(
os.environ,
{
"GSCU_TCP_LISTEN_HOST": "127.0.0.1",
"GSCU_TCP_PORT": str(port),
"GSCU_TCP_BIND_RETRY_SECONDS": "0.05",
},
):
gateway = TcpGateway()
try:
gateway.start()
self.assertTrue(self._wait_until(lambda: gateway.status()["listening"]))
gateway.close()
gateway.start()
self.assertTrue(
self._wait_until(lambda: gateway.status()["listening"]),
gateway.status(),
)
finally:
gateway.close()
def test_tcp_gateway_rejects_peer_accepted_during_close(self) -> None:
settimeout_entered = threading.Event()
allow_settimeout_to_finish = threading.Event()
accepted_socket_closed = threading.Event()
unexpected_reader_started = threading.Event()
allow_unexpected_reader_to_finish = threading.Event()
class AcceptedSocket:
def settimeout(self, timeout: float) -> None:
del timeout
settimeout_entered.set()
allow_settimeout_to_finish.wait(timeout=2)
def setsockopt(self, *args: object) -> None:
del args
def recv(self, size: int) -> bytes:
del size
unexpected_reader_started.set()
allow_unexpected_reader_to_finish.wait(timeout=2)
return b""
def close(self) -> None:
accepted_socket_closed.set()
accepted_socket = AcceptedSocket()
class ServerSocket:
def setsockopt(self, *args: object) -> None:
del args
def bind(self, address: object) -> None:
del address
def listen(self, backlog: int) -> None:
del backlog
def settimeout(self, timeout: float) -> None:
del timeout
def accept(self) -> tuple[AcceptedSocket, tuple[str, int]]:
return accepted_socket, ("127.0.0.1", 4444)
def close(self) -> None:
return
with patch.dict(
os.environ,
{
"GSCU_TCP_LISTEN_HOST": "127.0.0.1",
"GSCU_TCP_PORT": "4444",
"GSCU_TCP_BIND_RETRY_SECONDS": "0.05",
},
):
gateway = TcpGateway()
with patch.object(gateway, "_new_server_socket", return_value=ServerSocket()):
gateway.start()
self.assertTrue(settimeout_entered.wait(timeout=1))
close_thread = threading.Thread(target=gateway.close)
close_thread.start()
try:
self.assertTrue(self._wait_until(gateway._stop.is_set))
finally:
allow_settimeout_to_finish.set()
close_thread.join(timeout=3)
try:
self.assertFalse(close_thread.is_alive())
self.assertTrue(accepted_socket_closed.is_set())
self.assertFalse(unexpected_reader_started.is_set())
self.assertFalse(gateway.status()["connected"])
self.assertIsNone(gateway.status()["peer"])
finally:
allow_unexpected_reader_to_finish.set()
if __name__ == "__main__":
unittest.main()
+286
View File
@@ -0,0 +1,286 @@
from __future__ import annotations
import json
import os
import unittest
from unittest.mock import MagicMock, patch
from cognitive_agent.application.self_extension import (
ExtensionIntegrationError,
MAX_PLAN_DELAY_MS,
MAX_TOTAL_PLAN_DELAY_MS,
SelfExtensionService,
SimulationGateway,
)
class SelfExtensionDelayPlanTests(unittest.TestCase):
def setUp(self) -> None:
self.service = SelfExtensionService(SimulationGateway())
@staticmethod
def _vehicle(command: str) -> dict[str, object]:
return {
"kind": "vehicle",
"capability_id": "",
"command": command,
"label": command,
"delay_ms": 0,
}
@staticmethod
def _delay(value: object) -> dict[str, object]:
return {
"kind": "delay",
"capability_id": "",
"command": "",
"label": "wait",
"delay_ms": value,
}
def _plan(
self,
steps: list[object],
*,
peripherals: list[dict[str, object]] | None = None,
) -> dict[str, object]:
proposed = {
"summary": "test plan",
"needs_confirmation": False,
"question": "",
"steps": steps,
}
vehicle_commands = [
str(step.get("command"))
for step in steps
if isinstance(step, dict)
and step.get("kind") == "vehicle"
and step.get("command")
]
with patch.dict(os.environ, {"OPENAI_API_KEY": "offline-test-key"}, clear=False):
with patch.object(
self.service,
"_request_gpt_plan",
return_value=json.dumps(proposed, ensure_ascii=False),
):
return self.service.plan(
{
"utterance": "test utterance",
"peripherals": peripherals or [],
"vehicle_devices": [
{
"name": "test vehicle device",
"actions": [
{"name": command, "command": command}
for command in vehicle_commands
],
}
],
}
)
def test_preserves_action_delay_action_order_and_canonicalizes_label(self) -> None:
peripherals = [
{
"id": "cactus-on",
"status": "learned",
"device_name": "跳舞仙人掌",
"action": "开启",
"risk": "low",
},
{
"id": "cactus-off",
"status": "learned",
"device_name": "跳舞仙人掌",
"action": "关闭",
"risk": "low",
},
]
steps = [
{
"kind": "peripheral",
"capability_id": "cactus-on",
"command": "",
"label": "ignored",
"delay_ms": 0,
},
self._delay(5_000),
{
"kind": "peripheral",
"capability_id": "cactus-off",
"command": "",
"label": "ignored",
"delay_ms": 0,
},
]
result = self._plan(steps, peripherals=peripherals)
self.assertEqual(
[step["kind"] for step in result["steps"]],
["peripheral", "delay", "peripheral"],
)
self.assertEqual(result["steps"][1]["delay_ms"], 5_000)
self.assertEqual(result["steps"][1]["label"], "等待 5 秒")
self.assertEqual(result["steps"][1]["risk"], "low")
def test_rejects_non_integer_or_out_of_range_delay_values(self) -> None:
invalid_values: tuple[object, ...] = (
"5000",
True,
False,
1.5,
None,
0,
-1,
MAX_PLAN_DELAY_MS + 1,
)
for value in invalid_values:
with self.subTest(value=value):
with self.assertRaises(ExtensionIntegrationError):
self._plan(
[
self._vehicle("light ambient on"),
self._delay(value),
self._vehicle("light ambient off"),
]
)
def test_rejects_delay_at_plan_boundaries_or_next_to_another_delay(self) -> None:
invalid_plans = (
[self._delay(1), self._vehicle("ac on")],
[self._vehicle("ac on"), self._delay(1)],
[
self._vehicle("ac on"),
self._delay(1),
self._delay(1),
self._vehicle("ac off"),
],
)
for steps in invalid_plans:
with self.subTest(steps=steps):
with self.assertRaises(ExtensionIntegrationError):
self._plan(steps)
def test_rejects_delay_that_becomes_boundary_after_invalid_action_is_removed(self) -> None:
with self.assertRaisesRegex(ExtensionIntegrationError, "两个可执行动作之间"):
self._plan(
[
{
"kind": "peripheral",
"capability_id": "not-learned",
"command": "",
"label": "invalid",
"delay_ms": 0,
},
self._delay(1_000),
self._vehicle("ac off"),
]
)
def test_enforces_cumulative_delay_limit(self) -> None:
accepted = self._plan(
[
self._vehicle("ac on"),
self._delay(MAX_PLAN_DELAY_MS),
self._vehicle("ac temp driver 24"),
self._delay(MAX_PLAN_DELAY_MS),
self._vehicle("ac off"),
]
)
self.assertEqual(
sum(
step.get("delay_ms", 0)
for step in accepted["steps"]
if step["kind"] == "delay"
),
MAX_TOTAL_PLAN_DELAY_MS,
)
with self.assertRaisesRegex(ExtensionIntegrationError, "累计延时"):
self._plan(
[
self._vehicle("ac on"),
self._delay(MAX_PLAN_DELAY_MS),
self._vehicle("ac temp driver 24"),
self._delay(MAX_PLAN_DELAY_MS),
self._vehicle("ac fan 3"),
self._delay(1),
self._vehicle("ac off"),
]
)
def test_rejects_device_fields_on_delay_step(self) -> None:
for field_name, value in (("command", "wait 5 seconds"), ("capability_id", "cap-1")):
with self.subTest(field_name=field_name):
delay_step = self._delay(5_000)
delay_step[field_name] = value
with self.assertRaisesRegex(ExtensionIntegrationError, "不能包含"):
self._plan(
[
self._vehicle("ac on"),
delay_step,
self._vehicle("ac off"),
]
)
def test_rejects_implicit_or_unknown_delay_representation(self) -> None:
non_delay_with_wait = self._vehicle("ac on")
non_delay_with_wait["delay_ms"] = 5_000
with self.assertRaisesRegex(ExtensionIntegrationError, "非延时步骤"):
self._plan([non_delay_with_wait, self._vehicle("ac off")])
with self.assertRaisesRegex(ExtensionIntegrationError, "未知步骤类型"):
self._plan(
[
self._vehicle("ac on"),
{
"kind": "wait",
"capability_id": "",
"command": "",
"label": "wait",
"delay_ms": 5_000,
},
self._vehicle("ac off"),
]
)
def test_gpt_schema_and_prompt_define_millisecond_delay_contract(self) -> None:
response = MagicMock()
response.__enter__.return_value = response
response.read.return_value = json.dumps({"output_text": "{}"}).encode("utf-8")
with patch.object(self.service, "openai_streaming", False):
with patch(
"cognitive_agent.application.self_extension.urlopen",
return_value=response,
) as open_request:
self.service._request_gpt_plan(
"开启跳舞仙人掌,5s 后关闭", [], [], "test-key"
)
request_payload = json.loads(open_request.call_args.args[0].data.decode("utf-8"))
plan_format = request_payload["text"]["format"]
step_schema = plan_format["schema"]["properties"]["steps"]["items"]
self.assertTrue(plan_format["strict"])
self.assertIn("delay", step_schema["properties"]["kind"]["enum"])
self.assertIn("delay_ms", step_schema["required"])
self.assertEqual(
step_schema["properties"]["delay_ms"]["maximum"],
MAX_PLAN_DELAY_MS,
)
prompt = "\n".join(
content["text"]
for message in request_payload["input"]
if message.get("role") == "system"
for content in message.get("content", [])
if content.get("type") == "input_text"
)
self.assertIn("delay_ms=5000", prompt)
self.assertIn(str(MAX_TOTAL_PLAN_DELAY_MS), prompt)
self.assertIn("不能位于首尾", prompt)
self.assertIn("绝不能把等待时间写进 command", prompt)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,58 @@
from __future__ import annotations
from pathlib import Path
import unittest
REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
SOURCE_PATH = REPOSITORY_ROOT / "apps" / "web-console" / "self-extension.js"
class SelfExtensionFrontendDelayContractTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.source = SOURCE_PATH.read_text(encoding="utf-8")
hydrate_start = cls.source.index(" function hydratePlan(steps) {")
hydrate_end = cls.source.index("\n function setExecutionState", hydrate_start)
cls.hydrate_source = cls.source[hydrate_start:hydrate_end]
execute_start = cls.source.index(" async function executePlan(plan) {")
execute_end = cls.source.index("\n function addHistory", execute_start)
cls.execute_source = cls.source[execute_start:execute_end]
def test_hydration_recognizes_and_strictly_validates_delay_steps(self) -> None:
self.assertIn('if (step.kind === "delay")', self.hydrate_source)
self.assertIn("Number.isInteger(step.delay_ms)", self.hydrate_source)
self.assertIn("step.delay_ms < MIN_PLAN_DELAY_MS", self.hydrate_source)
self.assertIn("step.delay_ms > MAX_PLAN_DELAY_MS", self.hydrate_source)
self.assertIn("totalDelayMs > MAX_TOTAL_PLAN_DELAY_MS", self.hydrate_source)
self.assertIn("index === 0 || index === plan.length - 1", self.hydrate_source)
self.assertIn('plan[index - 1].kind === "delay"', self.hydrate_source)
self.assertIn("step.delay_ms !== undefined && step.delay_ms !== 0", self.hydrate_source)
self.assertNotIn(".filter(Boolean)", self.hydrate_source)
def test_hydration_displays_a_canonical_five_second_label(self) -> None:
self.assertIn("`等待 ${step.delay_ms / 1000} 秒`", self.hydrate_source)
self.assertIn("delayMs: step.delay_ms", self.hydrate_source)
def test_execution_routes_delay_only_to_the_timer(self) -> None:
delay_branch = self.execute_source.index('if (step.kind === "delay")')
vehicle_branch = self.execute_source.index('else if (step.kind === "vehicle")')
peripheral_branch = self.execute_source.index('await sendGatewayCommand("emit"')
self.assertLess(delay_branch, vehicle_branch)
self.assertLess(vehicle_branch, peripheral_branch)
between_delay_and_vehicle = self.execute_source[delay_branch:vehicle_branch]
self.assertIn("await delay(step.delayMs);", between_delay_and_vehicle)
self.assertNotIn("executeVehicle", between_delay_and_vehicle)
self.assertNotIn("sendGatewayCommand", between_delay_and_vehicle)
def test_explicit_delay_does_not_stack_with_default_action_spacing(self) -> None:
self.assertIn("const nextStep = plan[index + 1];", self.execute_source)
self.assertIn(
'nextStep && step.kind !== "delay" && nextStep.kind !== "delay"',
self.execute_source,
)
self.assertIn("await delay(state.settings.actionDelayMs);", self.execute_source)
if __name__ == "__main__":
unittest.main()
+141
View File
@@ -0,0 +1,141 @@
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.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()
+608
View File
@@ -0,0 +1,608 @@
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.closed = False
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_legacy_rear_lock_is_retired_before_compile(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"], "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.*0x32B.*observational only",
):
self.runtime.build_function_config(
"door", [action], "retired-rear-lock", "local_rules"
)
with self.assertRaisesRegex(
NativeCanCompileError, r"unsupported.*current CH1.*observational only"
):
self.runtime._frame_specs({"capability": action["capability"]})
self.assertEqual(_parse_exact_command("door rear lock"), [])
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")
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")
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_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()