feat: complete dual-seat whole-vehicle orchestration

This commit is contained in:
vee
2026-08-12 15:12:40 +08:00
parent 32379a1573
commit 9ce64b3a01
62 changed files with 9741 additions and 3950 deletions
@@ -166,18 +166,35 @@ class FrontendVehicleStateSyncContractTests(unittest.TestCase):
{"climate": state["climate"]},
)
def test_ch1_mode_never_polls_the_legacy_ch0_seat_endpoint(self) -> None:
sync_source = function_source(
def test_dual_channel_runtime_polls_each_seat_without_aliasing(self) -> None:
passenger_source = function_source(
self.app_source,
"async function synchronizePassengerSeatState() {",
"\nasync function synchronizeDriverSeatState()",
)
driver_source = function_source(
self.app_source,
"async function synchronizeDriverSeatState() {",
"\nasync function synchronizePhysicalSeatState()",
)
combined_source = function_source(
self.app_source,
"async function synchronizePhysicalSeatState() {",
"\nasync function loadAuthStatus()",
)
guard = sync_source.index(
'target_policy === "ch1_body_can_driver_only"'
self.assertIn("state.runtime?.hardware?.configured", passenger_source)
self.assertIn("/api/hardware/seat-state", passenger_source)
self.assertIn("/api/hardware/vehicle-state", driver_source)
self.assertLess(
combined_source.index("synchronizePassengerSeatState()"),
combined_source.index("synchronizeDriverSeatState()"),
)
legacy_request = sync_source.index("/api/hardware/seat-state")
self.assertLess(guard, legacy_request)
self.assertIn("return false", sync_source[guard:legacy_request])
render_source = function_source(
self.app_source,
"function renderSeatState(actuatorState) {",
"\nfunction renderDomainState",
)
self.assertNotIn("passenger: mergedActuatorState.driver", render_source)
def test_vehicle_selection_cannot_replay_unfiltered_state_in_ch1_mode(self) -> None:
select_source = function_source(
+53
View File
@@ -356,6 +356,59 @@ operations:
[{"name": "transmittance_pct", "value": 20}],
)
def test_delete_user_mode_removes_yaml_and_protects_presets(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
preset_path = root / "common" / "preset" / "office.yaml"
preset_path.parent.mkdir(parents=True)
preset_path.write_text(
"""
id: office
name: 办公模式
operations:
- domain: climate
capability: climate.set_temperature
target_scope: vehicle
parameters:
temperature_c: 24
""".strip()
+ "\n",
encoding="utf-8",
)
registry = ModeProfileRegistry(root)
user_profile = registry.save_mode(
"K歌模式",
[
{
"domain": "ambient_light",
"capability": "ambient_light.set_power",
"target_scope": "vehicle",
"parameters": {"state": "on"},
}
],
vehicle_id="denza_n7",
)
public_modes = {
mode["id"]: mode
for mode in registry.public_modes(vehicle_id="denza_n7")
}
self.assertEqual(public_modes[user_profile.id]["origin"], "user")
self.assertTrue(public_modes[user_profile.id]["deletable"])
self.assertEqual(public_modes["office"]["origin"], "preset")
self.assertFalse(public_modes["office"]["deletable"])
deleted = registry.delete_user_mode(user_profile.id, "denza_n7")
self.assertEqual(deleted.id, user_profile.id)
self.assertFalse(Path(user_profile.source).exists())
self.assertNotIn(
user_profile.id,
{mode["id"] for mode in registry.public_modes("denza_n7")},
)
with self.assertRaises(PermissionError):
registry.delete_user_mode("office", "denza_n7")
def test_update_mode_profile_backs_up_initial_yaml_and_restores(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
+22 -1
View File
@@ -11,7 +11,7 @@ from cognitive_agent.application.native_can_runtime import (
NativeCanRuntime,
)
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.infrastructure.can.gateway import CanFrame
from cognitive_agent.infrastructure.can.gateway import CanFrame, ZlgChannelConfig
from cognitive_agent.infrastructure.can.hardware import VehicleCanSettings
@@ -253,6 +253,27 @@ class NativeCanIntegrationTests(unittest.TestCase):
self.assertFalse(settings.enabled)
def test_dual_channel_formats_are_explicit_and_independent(self) -> None:
seat = ZlgChannelConfig(
channel=0,
frame_format="classic_can",
arbitration_bitrate=500_000,
data_bitrate=2_000_000,
termination_enabled=True,
)
vehicle = ZlgChannelConfig(
channel=1,
frame_format="can_fd",
arbitration_bitrate=500_000,
data_bitrate=2_000_000,
termination_enabled=False,
)
self.assertEqual(seat.public()["frame_format"], "classic_can")
self.assertEqual(vehicle.public()["frame_format"], "can_fd")
self.assertNotEqual(seat.channel, vehicle.channel)
self.assertNotEqual(seat.termination_enabled, vehicle.termination_enabled)
if __name__ == "__main__":
unittest.main()
+82
View File
@@ -190,6 +190,88 @@ class NativeStateSyncTests(unittest.TestCase):
)
self.assertEqual(after, before)
def test_verified_read_only_ch1_feedback_updates_only_driver(self) -> None:
state = self.store.synchronize_physical_ch1_feedback(
"conv-native-sync",
[{
"source": "physical_can_ch1",
"channel": 1,
"frame_id": "0x4FA",
"feedback_frame_id": "0x4FA",
"valid": True,
"verification": {
"authoritative": True,
"channel": 1,
"simulated": False,
"read_only": True,
"frame_id": "0x4FA",
"payload_size": 8,
},
"actuator_state": {
"driver": {"ventilation": {"state": "on", "level": 3}}
},
}],
)
self.assertEqual(state["devices"]["driver"]["ventilation"]["level"], 3)
self.assertEqual(
state["devices"]["driver"]["ventilation"]["feedback_source"],
"physical_can_ch1",
)
self.assertEqual(state["devices"]["passenger"]["ventilation"]["state"], "off")
def test_verified_vehicle_safety_and_door_feedback_feed_whole_vehicle_state(self) -> None:
def feedback(frame_id: str, **state_patch: dict) -> dict:
return {
"source": "physical_can_ch1",
"channel": 1,
"frame_id": frame_id,
"feedback_frame_id": frame_id,
"valid": True,
"verification": {
"authoritative": True,
"channel": 1,
"simulated": False,
"read_only": True,
"frame_id": frame_id,
"payload_size": 8,
},
**state_patch,
}
state = self.store.synchronize_physical_ch1_feedback(
"conv-native-sync",
[
feedback("0x342", vehicle_safety={"gear": "P"}),
feedback("0x4EB", vehicle_safety={"speed_kmh": 0.0}),
feedback(
"0x407",
domain_state={
"door": {
"locks": {
"front_left": {"state": "lock"},
"front_right": {"state": "unlock"},
"rear_left": {"state": "lock"},
"rear_right": {"state": "lock"},
},
"vehicle_lock": {"state": "unknown"},
}
},
),
],
)
vehicle_status = state["domain_devices"]["vehicle_status"]
self.assertEqual(vehicle_status["gear"], "P")
self.assertEqual(vehicle_status["speed_kmh"], 0.0)
self.assertTrue(vehicle_status["stationary"])
self.assertEqual(vehicle_status["feedback_source"], "physical_can_ch1")
door = state["domain_devices"]["door"]
self.assertEqual(door["locks"]["front_right"]["state"], "unlock")
self.assertEqual(
door["locks"]["front_right"]["feedback_source"],
"physical_can_ch1",
)
def test_unverified_or_empty_feedback_never_updates_state(self) -> None:
before = self.store.snapshot("conv-native-sync")
for feedback in (
+12
View File
@@ -9,6 +9,7 @@ from unittest.mock import patch
from cognitive_agent.infrastructure.llm.openai_responses import (
SYSTEM_PROMPT,
OpenAIIntentInterpreter,
build_intent_schema,
)
@@ -20,6 +21,7 @@ INTENT = {
"capability": "seat.set_ventilation",
"target_scope": "passenger",
"operation": "activate",
"duration_ms": None,
"parameters": [
{"name": "state", "value": "on"},
{"name": "level", "value": 1},
@@ -76,6 +78,16 @@ class OpenAIResponsesTests(unittest.TestCase):
self.assertIn("longitudinal 20%", normalized)
self.assertIn("do not invent catalog_only capabilities", normalized)
def test_explicit_seat_motion_duration_is_part_of_the_model_contract(self) -> None:
normalized = " ".join(SYSTEM_PROMPT.split())
self.assertIn("preserve that duration exactly in request.duration_ms", normalized)
request_schema = build_intent_schema({})["properties"]["requests"]["items"]
self.assertIn("duration_ms", request_schema["required"])
self.assertEqual(
request_schema["properties"]["duration_ms"]["type"],
["integer", "null"],
)
def test_non_streaming_structured_output(self) -> None:
response_body = json.dumps({"output_text": json.dumps(INTENT)})
environment = {
+142 -2
View File
@@ -19,6 +19,7 @@ from cognitive_agent.application.self_extension import (
build_gateway_command,
validate_slot,
)
from cognitive_agent.application.zcan_seat_executor import ZcanBridgeSeatExecutor
class SelfExtensionTests(unittest.TestCase):
@@ -138,7 +139,7 @@ class SelfExtensionTests(unittest.TestCase):
"_request_gpt_plan",
return_value=json.dumps(proposed),
):
with self.assertRaisesRegex(ExtensionIntegrationError, "CH1"):
with self.assertRaisesRegex(ExtensionIntegrationError, "双通道"):
service.plan(
{
"utterance": "打开座椅通风",
@@ -225,7 +226,59 @@ class SelfExtensionTests(unittest.TestCase):
command_schema = request_payload["text"]["format"]["schema"]["properties"][
"steps"
]["items"]["properties"]["command"]
self.assertEqual(command_schema["enum"], ["", "ac off"])
self.assertEqual(command_schema["type"], "string")
self.assertEqual(command_schema["maxLength"], 300)
def test_plan_accepts_bounded_passenger_position_for_declared_ch0_executor(
self,
) -> None:
service = SelfExtensionService(SimulationGateway())
proposed = {
"summary": "调整副驾",
"needs_confirmation": False,
"question": "",
"steps": [
{
"kind": "vehicle",
"capability_id": "",
"command": "副驾水平调到63%",
"label": "副驾水平位置 63%",
"delay_ms": 0,
}
],
}
payload = {
"utterance": "副驾水平位置调到63%",
"peripherals": [],
"vehicle_devices": [
{
"id": "passenger-seat",
"name": "副驾座椅",
"channel": "CH0",
"actions": [
{"name": "副驾水平 50%", "command": "副驾水平调到50%"}
],
}
],
}
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False):
with patch.object(
service,
"_request_gpt_plan",
return_value=json.dumps(proposed, ensure_ascii=False),
):
result = service.plan(payload)
self.assertEqual(result["steps"][0]["command"], "副驾水平调到63%")
proposed["steps"][0]["command"] = "副驾水平调到101%"
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False):
with patch.object(
service,
"_request_gpt_plan",
return_value=json.dumps(proposed, ensure_ascii=False),
):
with self.assertRaisesRegex(ExtensionIntegrationError, "双通道"):
service.plan(payload)
def test_vehicle_execution_reuses_the_same_orchestration_service(self) -> None:
with patch.dict(
@@ -515,6 +568,93 @@ class SelfExtensionTests(unittest.TestCase):
self.assertFalse(rejected["success"])
self.assertFalse(rejected["physical_feedback_confirmed"])
def test_ch0_passenger_feedback_requires_shared_runtime_physical_snapshot(
self,
) -> None:
operation = {
"ref": "passenger-longitudinal-63",
"domain": "seat",
"status": "succeeded",
"telemetry": {
"transport": "shared_can_runtime",
"channel": 0,
"simulated": False,
"hardware_feedback": True,
"feedback_required": True,
"feedback_observed": True,
"feedback_match": True,
"physical_state": {
"source": "physical_can",
"channel": 0,
"target": {
"device": "seat",
"zone": "front_right",
"instance": "passenger",
},
"position": {
"longitudinal": {
"position_pct": 63,
"position_valid": True,
}
},
},
},
}
verified = SelfExtensionService._authoritative_vehicle_feedback_state(
operation
)
self.assertIsNotNone(verified)
self.assertEqual(verified["verification"]["channel"], 0)
operation["telemetry"]["transport"] = "untrusted_bridge"
self.assertIsNone(
SelfExtensionService._authoritative_vehicle_feedback_state(operation)
)
def test_ch0_bridge_report_exposes_verified_passenger_snapshot(self) -> None:
function_config = {
"metadata": {"request_id": "req-ch0-extension"},
"spec": {
"operations": [
{
"id": "passenger-longitudinal-63",
"capability": "seat.set_longitudinal_position",
}
]
},
}
bridge_result = {
"result": "PASS",
"details": {
"summary": "物理反馈达到目标",
"motors": [
{
"position": 6300 if index == 0 else 5000,
"position_valid": True,
"active": False,
}
for index in range(4)
],
"thermal": [0x01],
},
}
report = ZcanBridgeSeatExecutor._report(
function_config,
"2026-08-11T00:00:00Z",
bridge_result,
None,
)
physical_state = report["spec"]["physical_state"]
self.assertEqual(physical_state["source"], "physical_can")
self.assertEqual(physical_state["channel"], 0)
self.assertEqual(
physical_state["position"]["longitudinal"]["position_pct"], 63
)
verified = SelfExtensionService._authoritative_vehicle_feedback_state(
report["spec"]["results"][0]
)
self.assertIsNotNone(verified)
def test_vehicle_execution_ignores_browser_routing_overrides(self) -> None:
with patch.dict(
os.environ,
+180
View File
@@ -13,8 +13,12 @@ from cognitive_agent.application.self_repair.healing_model import (
public_scenarios,
)
from cognitive_agent.application.self_repair.service import SelfRepairService
from cognitive_agent.application.self_repair.can_runtime import (
StandaloneCanRuntimeManager,
)
from cognitive_agent.application.self_repair.signal_diagnostic import SignalDiagnosticModel
from cognitive_agent.application.self_repair.zcan_bridge import ZcanBridgeManager
from cognitive_agent.infrastructure.can.gateway import ZlgChannelConfig
def test_six_production_cases_complete_the_shadow_repair_loop() -> None:
@@ -104,3 +108,179 @@ def test_bridge_manager_does_not_restart_a_live_busy_request(tmp_path: Path) ->
assert manager._heartbeat_online() is False
assert manager._bridge_busy_within_grace() is True
def test_standalone_runtime_opens_one_owner_for_seat_and_vehicle_channels(
tmp_path: Path,
) -> None:
required_version = "runtime-test-v1"
bridge_script = tmp_path / "seat_bridge.py"
bridge_script.write_text(
"BRIDGE_VERSION = 'runtime-test-v1'\n"
"def run_bridge(transport=None, stop_event=None):\n"
" stop_event.wait(2.0)\n",
encoding="utf-8",
)
python_executable = tmp_path / "python.exe"
zlg_bridge_script = tmp_path / "zlg_bridge.py"
dll_path = tmp_path / "zlgcan.dll"
for path in (python_executable, zlg_bridge_script, dll_path):
path.write_bytes(b"test")
owners = []
class FakeBus:
def __init__(self, channel, config):
self.channel = channel
self.config = config
def send(self, *args, **kwargs):
return None
def receive(self, *args, **kwargs):
return None
class FakeOwner:
def __init__(self, **kwargs):
self.kwargs = kwargs
self.closed = False
self.configs = {item.channel: item for item in kwargs["channels"]}
owners.append(self)
def channel_bus(self, channel):
return FakeBus(channel, self.configs[channel])
def close(self):
self.closed = True
manager = StandaloneCanRuntimeManager(
tmp_path / "status.json",
bridge_script,
required_version=required_version,
python_executable=python_executable,
zlg_bridge_script=zlg_bridge_script,
dll_path=dll_path,
device_type=41,
device_index=0,
channels=[
ZlgChannelConfig(channel=0, frame_format="classic_can"),
ZlgChannelConfig(channel=1, frame_format="can_fd"),
],
seat_channel=0,
poll_s=0.01,
process_factory=FakeOwner,
)
try:
assert manager.configured is True
assert manager.channel_bus(1, timeout_s=1.0).channel == 1
manager.ensure_async()
assert len(owners) == 1
assert [
item.frame_format for item in owners[0].kwargs["channels"]
] == ["classic_can", "can_fd"]
assert manager.snapshot()["backend"] == "standalone_zlg"
finally:
manager.stop()
assert owners[0].closed is True
def _qualified_runtime_status(*, build_id: int = 16, frame_age_s: float = 0.0):
now = time.time()
frame_epoch = now - frame_age_s
return {
"bridge_version": "runtime-test-v1",
"heartbeat_epoch": now,
"current_request_id": None,
"system": {"firmware_revision": 15},
"firmware_identity": {
"firmware_build_id": build_id,
"qualified": build_id == 16,
},
"frame_epochs": {
frame_id: frame_epoch
for frame_id in (
"0x301", "0x302", "0x303", "0x304", "0x305", "0x306", "0x307"
)
},
}
def test_standalone_runtime_rejects_stale_protocol_and_old_firmware_build(
tmp_path: Path,
) -> None:
manager = StandaloneCanRuntimeManager(
tmp_path / "status.json",
tmp_path / "seat_bridge.py",
required_version="runtime-test-v1",
python_executable=tmp_path / "python.exe",
zlg_bridge_script=tmp_path / "zlg_bridge.py",
dll_path=tmp_path / "zlgcan.dll",
device_type=41,
device_index=0,
channels=[ZlgChannelConfig(channel=0, frame_format="classic_can")],
seat_channel=0,
expected_firmware_revision=15,
expected_firmware_build_id=16,
)
manager.status_path.write_text(
json.dumps(_qualified_runtime_status(frame_age_s=5.0)), encoding="utf-8"
)
ready, reason, _ = manager._protocol_status()
assert ready is False
assert "过期" in reason
manager.status_path.write_text(
json.dumps(_qualified_runtime_status(build_id=15)), encoding="utf-8"
)
ready, reason, _ = manager._protocol_status()
assert ready is False
assert "build" in reason
def test_standalone_runtime_restores_repository_firmware_before_request(
tmp_path: Path,
) -> None:
class FakeProgrammer:
def __init__(self):
self.calls = 0
def program(self):
self.calls += 1
return {"programmed": True, "image_sha256": "f" * 64}
programmer = FakeProgrammer()
manager = StandaloneCanRuntimeManager(
tmp_path / "status.json",
tmp_path / "seat_bridge.py",
required_version="runtime-test-v1",
python_executable=tmp_path / "python.exe",
zlg_bridge_script=tmp_path / "zlg_bridge.py",
dll_path=tmp_path / "zlgcan.dll",
device_type=41,
device_index=0,
channels=[ZlgChannelConfig(channel=0, frame_format="classic_can")],
seat_channel=0,
firmware_programmer=programmer,
firmware_auto_flash=True,
expected_firmware_revision=15,
expected_firmware_build_id=16,
firmware_boot_wait_s=0,
protocol_probe_timeout_s=0.01,
startup_timeout_s=0.2,
)
manager.status_path.write_text(
json.dumps(_qualified_runtime_status(build_id=15)), encoding="utf-8"
)
manager.ensure_async = lambda: None
manager._stop_runtime_owner = lambda: None
manager._attempt_start = lambda: manager.status_path.write_text(
json.dumps(_qualified_runtime_status()), encoding="utf-8"
)
status = manager.ensure_seat_protocol()
assert status["firmware_identity"]["firmware_build_id"] == 16
assert programmer.calls == 1
assert manager.snapshot()["firmware_recovery_count"] == 1
+6 -4
View File
@@ -117,10 +117,12 @@ class VehicleSafetyPreflightTests(unittest.TestCase):
bus = _ReceiveOnlyBus([_gear(4), _speed(0)])
bridge = _Bridge(bus)
with patch.object(
type(service.vehicle_can_settings),
"bridge",
return_value=bridge,
with patch.dict(
"os.environ",
{"COGNITIVE_AGENT_SHARED_CAN_RUNTIME_ENABLED": "false"},
clear=False,
), patch.object(
type(service.vehicle_can_settings), "bridge", return_value=bridge
):
with self.assertRaisesRegex(HardwareSafetyError, "not confirmed in P"):
service._execute_orchestrated_reports(