feat: add seat firmware workbench and OTA delivery

This commit is contained in:
vee
2026-09-16 17:55:30 +08:00
parent 3eeca2ea75
commit ab8e43050c
70 changed files with 22594 additions and 316 deletions
@@ -0,0 +1,321 @@
"""A mode device list is an additive configuration edit, never live vehicle control."""
from __future__ import annotations
import hashlib
import json
from copy import deepcopy
from unittest.mock import MagicMock, patch
import pytest
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.assistant_plan_guard import MAX_PLAN_REQUESTS
from cognitive_agent.application.codex_assistant import (
CodexAssistantService,
_authorize_vehicle_request,
)
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
@pytest.fixture
def addition_service(tmp_path, monkeypatch):
for variable in (
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED", "COGNITIVE_AGENT_VEHICLE_CAN_ENABLED",
"COGNITIVE_AGENT_WIRED_PASSENGER_CAN_ENABLED", "COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED",
):
monkeypatch.setenv(variable, "false")
service = SeatDemoService(REPOSITORY_ROOT)
service.artifact_root = tmp_path / "runs"
user_root = tmp_path / "user-modes"
service.mode_profiles = ModeProfileRegistry(REPOSITORY_ROOT / "config" / "modes", user_root=user_root)
try:
with (
patch.object(service.hardware_execution, "reserve", side_effect=AssertionError("mode edit reached real CAN")) as reserve,
patch.object(service._vehicle_mqtt_transport, "bus", side_effect=AssertionError("mode edit reached MQTT")) as mqtt,
patch.object(service.openai, "interpret", side_effect=AssertionError("deterministic mode edit contacted a model")),
):
yield service, AssistantCapabilityCatalog(service), user_root
reserve.assert_not_called()
mqtt.assert_not_called()
finally:
service.close()
def _preview(service, text, *, draft=None, plan=None, conversation="conv-device-additions"):
return service.preview_native_can(
text, provider="local", conversation_id=conversation, vehicle_id="denza_n7",
execution_mode="hardware", mode_profile_draft_context=deepcopy(draft), assistant_plan=plan,
)
def _actions(result):
return (result.get("intent_ir") or {}).get("spec", {}).get("actions", [])
def _action_map(result):
return {
(action["capability"], action["target"].get("instance") or action["target"].get("zone")):
action["parameters"] for action in _actions(result)
}
def _profile_map(profile):
return {(op.capability, op.target_scope): op.parameters for op in profile.operations}
def _seat_actions(mapping):
return {key: value for key, value in mapping.items() if key[0].startswith("seat.")}
def _reference(result):
return {key: result["mode_profile_draft"][key] for key in ("name", "source_request_id", "vehicle_id")}
def _hashes(root):
return {str(path): hashlib.sha256(path.read_bytes()).hexdigest() for path in root.rglob("*.yaml")}
def _seed_old_seat_only_draft(service, *, scope="passenger", name="躺平模式", extra=""):
# A deliberately explicit, seat-only request represents an older legitimate
# preview without relying on ignored developer artifacts or a model stub.
settings = (
"副驾座椅水平向前调整到20%,副驾靠背调整到45%,副驾座椅坐盆调整到30%,副驾腿托调整到70%"
if scope == "passenger" else "主驾座椅通风调整到低档,主驾座椅加热调整到低档"
)
result = _preview(service, f"构建一个{name},{settings}{extra}")
_assert_draft(result)
assert {target for _, target in _seat_actions(_action_map(result))} == {scope}
return result
def _assert_draft(result):
assert result["status"] == "succeeded", (result.get("mode_creation_failure"), result.get("validation"))
assert result["orchestration_route"] == "mode_profile_create_preview"
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert result.get("mode_profile_draft")
assert len(_actions(result)) == result["mode_profile_draft"]["operation_count"]
assert len(_actions(result)) <= MAX_PLAN_REQUESTS
assert len(_action_map(result)) == len(_actions(result)), "append duplicated an existing action key"
def _assistant(service, catalog, message, *, draft=None):
client = MagicMock()
client.available = False
assistant = CodexAssistantService(client)
snapshot = catalog.snapshot(vehicle_id="denza_n7")
fast = catalog.fast_answer(message, snapshot)
raw = []
def execute(instruction, progress, assistant_plan=None):
result = _preview(service, instruction, draft=draft, plan=assistant_plan)
raw.append(result)
return result
answer = assistant.stream(
message, conversation_id="conv-device-additions", principal="device-additions-test", history=[],
emit=lambda _event: True, execute_control=execute,
authoritative_answer=fast.answer if fast else None,
capability_snapshot=snapshot, capability_context=catalog.compact_context(snapshot),
scene_capability_context=catalog.scene_composition_context(snapshot),
control_terms=catalog.control_terms(snapshot), mode_profile_draft_context=draft,
)
assert len(raw) == 1, answer
client.start_structured_turn.assert_not_called()
client.start_turn.assert_not_called()
return answer, raw[0]
@pytest.mark.parametrize("message", ["构建一个躺平模式", "帮我构建一个躺平模式吧", "构建一个主驾躺平模式"])
def test_flat_mode_fast_template_includes_requested_cabin_domains(addition_service, message):
service, catalog, user_root = addition_service
answer, result = _assistant(service, catalog, message)
assert answer["success"] is True, answer
assert answer["route"] == "scene_template"
_assert_draft(result)
assert {"seat", "climate", "ambient_light", "interior_light", "window"} <= {
action["domain"] for action in _actions(result)
}
assert {target for _, target in _seat_actions(_action_map(result))} == (
{"driver"} if "主驾" in message else {"passenger"}
)
assert not _hashes(user_root)
@pytest.mark.parametrize("message", [
"在躺平模式中加入空调、氛围灯、阅读灯、车窗等设备动作",
"帮我给躺平模式加上空调、氛围灯、阅读灯和车窗",
"给躺平模式增加空调、氛围灯、阅读灯、车窗的联动",
"躺平模式中补充空调、氛围灯、阅读灯和车窗动作",
"调整躺平模式,加入空调、氛围灯、阅读灯、车窗",
])
@pytest.mark.parametrize("scope", ["passenger", "driver"])
def test_device_list_addition_revises_old_draft_without_execution_or_losing_seat_parameters(addition_service, message, scope):
service, catalog, user_root = addition_service
original = _seed_old_seat_only_draft(service, scope=scope)
before = deepcopy(_action_map(original))
reference = _reference(original)
authorization = _authorize_vehicle_request(message, mode_profile_draft_context=reference)
assert authorization.reason == "explicit_mode_profile_draft_update"
answer, revised = _assistant(service, catalog, message, draft=reference)
assert answer["success"] is True, answer
_assert_draft(revised)
assert _seat_actions(_action_map(revised)) == _seat_actions(before)
assert {"climate", "ambient_light", "interior_light", "window"} <= {
action["domain"] for action in _actions(revised)
}
assert revised["mode_profile_draft"]["revises_source_request_id"] == original["request_id"]
assert revised["mode_profile_draft"]["draft_revision"] == 2
assert _action_map(original) == before
assert not _hashes(user_root)
def test_repeated_device_list_is_idempotent_and_keeps_existing_custom_values(addition_service):
service, _, user_root = addition_service
original = _seed_old_seat_only_draft(service, extra=",空调调整到26度,关闭氛围灯")
message = "在躺平模式中加入空调、氛围灯、阅读灯、车窗等设备动作"
first = _preview(service, message, draft=_reference(original))
_assert_draft(first)
for key, params in _action_map(original).items():
assert _action_map(first)[key] == params
second = _preview(service, message, draft=_reference(first))
_assert_draft(second)
assert _action_map(second) == _action_map(first)
assert second["mode_profile_draft"]["draft_revision"] == 3
assert not _hashes(user_root)
@pytest.mark.parametrize("scope", ["passenger", "driver"])
def test_saved_mode_addition_changes_only_selected_profile_and_can_be_replayed(addition_service, scope):
service, _, user_root = addition_service
original = _seed_old_seat_only_draft(service, scope=scope)
draft = original["mode_profile_draft"]
saved = service.create_mode_profile_card(draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"])
mode_id = saved["mode_profile"]["id"]
unrelated = service.mode_profiles.save_mode("不会改变的模式", [{
"domain": "climate", "capability": "climate.set_temperature", "target_scope": "vehicle",
"parameters": {"temperature_c": 23},
}], vehicle_id="denza_n7")
unrelated_revision = service.mode_profiles.profile_revision(unrelated)
before = _profile_map(service.mode_profiles.get_profile(mode_id, "denza_n7"))
updated = _preview(service, "在躺平模式中加入空调、氛围灯、阅读灯、车窗等设备动作")
assert updated["status"] == "succeeded", updated.get("validation")
assert updated["orchestration_route"] == "mode_profile_update"
assert updated["execution_mode"] == "dry_run"
assert updated["hardware_execution_allowed"] is False
after = service.mode_profiles.get_profile(mode_id, "denza_n7")
assert _seat_actions(_profile_map(after)) == _seat_actions(before)
assert {"climate", "ambient_light", "interior_light", "window"} <= {op.domain for op in after.operations}
assert service.mode_profiles.profile_revision(service.mode_profiles.get_profile(unrelated.id, "denza_n7")) == unrelated_revision
assert len(_hashes(user_root)) == 2
replayed = service.execute_mode_profile_card(mode_id, execution_mode="dry_run", vehicle_id="denza_n7")
assert replayed["status"] == "succeeded"
assert _seat_actions(_action_map(replayed)) == _seat_actions(before)
@pytest.mark.parametrize("mutation", ["request_id", "vehicle", "name", "conversation"])
def test_invalid_or_stale_context_never_becomes_live_control_or_changes_saved_mode(addition_service, mutation):
service, _, user_root = addition_service
original = _seed_old_seat_only_draft(service)
reference = _reference(original)
conversation = "conv-device-additions"
if mutation == "request_id":
reference["source_request_id"] = "req-missing-old-source"
elif mutation == "vehicle":
reference["vehicle_id"] = "other_vehicle"
elif mutation == "name":
reference["name"] = "其他模式"
else:
conversation = "conv-another-conversation"
try:
result = _preview(service, "在躺平模式中加入空调和车窗", draft=reference, conversation=conversation)
except ValueError:
pass
else:
assert result["status"] != "succeeded"
assert not result.get("mode_profile_draft")
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert not _actions(result)
assert not result.get("command_plans")
assert not _hashes(user_root)
@pytest.mark.parametrize("message", [
"在躺平模式中加入空调和后备箱动作",
"在躺平模式中加入阅读灯和天窗动作",
"在躺平模式中加入空调并把温度调整到99度",
])
def test_unsupported_or_out_of_range_addition_rejects_all_changes_atomically(addition_service, message):
service, _, user_root = addition_service
original = _seed_old_seat_only_draft(service)
try:
result = _preview(service, message, draft=_reference(original))
except ValueError:
pass
else:
assert result["status"] != "succeeded"
assert not result.get("mode_profile_draft")
assert not _actions(result)
assert not result.get("command_plans")
assert result["hardware_execution_allowed"] is False
assert not _hashes(user_root)
def test_adding_devices_to_full_twelve_action_draft_never_truncates_original_actions(addition_service):
service, _, user_root = addition_service
operations = [
("climate.set_power", "vehicle", {"state": "on"}),
("climate.set_temperature", "vehicle", {"temperature_c": 24}),
("climate.set_fan_level", "vehicle", {"level": 2}),
("climate.set_cycle_mode", "vehicle", {"mode": "internal"}),
("climate.set_control_mode", "vehicle", {"mode": "auto"}),
("climate.set_front_defrost", "vehicle", {"state": "off"}),
("roof.set_shade_position", "vehicle", {"position_pct": 0}),
("window.set_position", "front_left", {"position_pct": 0}),
("window.set_position", "front_right", {"position_pct": 0}),
("ambient_light.set_power", "vehicle", {"state": "off"}),
("interior_light.set_indoor_power", "vehicle", {"state": "off"}),
("interior_light.set_reading_power", "all", {"state": "off"}),
]
plan = {
"detected_domains": sorted({cap.split(".", 1)[0] for cap, _, _ in operations}),
"requests": [{
"domain": cap.split(".", 1)[0], "capability": cap, "target_scope": target,
"operation": "set", "parameters": [{"name": name, "value": value} for name, value in params.items()],
"duration_ms": None, "reason": "固定目标边界回归。",
} for cap, target, params in operations],
"ambiguities": [], "confidence": 0.95, "decision_summary": ["验证12项原动作保留。"],
}
original = _preview(service, "构建一个满载参数模式", plan=plan)
_assert_draft(original)
assert len(_actions(original)) == 12
source_path = service.artifact_root / original["request_id"] / "result.json"
source_hash = hashlib.sha256(source_path.read_bytes()).hexdigest()
try:
result = _preview(service, "在满载参数模式中加入副驾座椅通风动作", draft=_reference(original))
except ValueError:
pass
else:
assert result["status"] != "succeeded"
assert not result.get("mode_profile_draft")
assert not _actions(result)
assert hashlib.sha256(source_path.read_bytes()).hexdigest() == source_hash
assert not _hashes(user_root)
def test_previous_artifact_without_new_metadata_remains_an_editable_server_owned_draft(addition_service):
service, _, user_root = addition_service
original = _seed_old_seat_only_draft(service)
legacy = deepcopy(original)
legacy["mode_profile_draft"].pop("draft_revision", None)
legacy["mode_profile_draft"].pop("generation_source", None)
service._persist(legacy)
result = _preview(service, "在躺平模式中加入空调、氛围灯、阅读灯、车窗", draft=_reference(legacy))
_assert_draft(result)
assert result["mode_profile_draft"]["draft_revision"] == 2
assert _seat_actions(_action_map(result)) == _seat_actions(_action_map(original))
assert not _hashes(user_root)
assert json.loads((service.artifact_root / original["request_id"] / "result.json").read_text(encoding="utf-8"))["request_id"] == original["request_id"]