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
+132
View File
@@ -0,0 +1,132 @@
"""Run the opt-in CH1 driver/body-CAN front-end HIL suite."""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src"
SEAT_SRC = ROOT / "domains" / "seat" / "src"
for source in (SRC, SEAT_SRC):
if str(source) not in sys.path:
sys.path.insert(0, str(source))
from cognitive_agent.config_loader import load_local_env # noqa: E402
from cognitive_agent.infrastructure.can.hardware import VehicleCanSettings # noqa: E402
from cognitive_agent.testing.ch1_hil import ( # noqa: E402
Ch1HilRunner,
JsonApiClient,
load_suite,
observe_stationary_ch1,
require_explicit_hil_arm,
suite_cases,
suite_rejections,
validate_ui_contract,
)
DEFAULT_SUITE = ROOT / "tests" / "fixtures" / "ch1_vehicle_frontend_hil_suite.json"
DEFAULT_OUTPUT = ROOT / "artifacts" / "hil" / "ch1_vehicle_frontend_hil_latest.json"
def passive_reader(token: str | None):
"""Build one fresh observer that opens CH1 only and never transmits."""
settings = VehicleCanSettings.from_env(ROOT)
settings.authorize(token)
if settings.channel != 1 or settings.required_channel != 1:
raise RuntimeError("passive preflight refused a non-CH1 channel")
def read() -> dict:
bridge = settings.bridge([settings.channel_config()])
try:
bus = bridge.channel_bus(1)
return observe_stationary_ch1(bus, timeout_seconds=5.0)
finally:
bridge.close()
return read
def offline_validate(suite_path: Path) -> int:
suite = load_suite(suite_path)
validate_ui_contract(
"self_orchestration",
(ROOT / "apps" / "web-console" / "app.js").read_text(encoding="utf-8"),
)
validate_ui_contract(
"self_extension",
(ROOT / "apps" / "web-console" / "self-extension.js").read_text(
encoding="utf-8"
),
)
counts = {
module: {
"total": len(suite_cases(suite, module)),
"physical": sum(
item.get("hil", True) for item in suite_cases(suite, module)
),
"contract_only": sum(
not item.get("hil", True) for item in suite_cases(suite, module)
),
"offline_rejections": len(suite_rejections(suite, module)),
}
for module in ("self_orchestration", "self_extension")
}
print(json.dumps({"status": "validated_offline", "cases": counts}, ensure_ascii=False))
return 0
def main() -> int:
parser = argparse.ArgumentParser(
description="CH1-only driver/body-CAN front-end HIL verification"
)
parser.add_argument("--suite", type=Path, default=DEFAULT_SUITE)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
parser.add_argument("--base-url", default="http://127.0.0.1:8000")
parser.add_argument("--offline-validate", action="store_true")
parser.add_argument(
"--confirm",
default="",
help="Live mode requires the exact text CH1_DRIVER_ONLY.",
)
args = parser.parse_args()
if args.offline_validate:
return offline_validate(args.suite)
# Loading the repository launch environment does not arm HIL by itself;
# the dedicated environment and exact CLI confirmation are both mandatory.
load_local_env(ROOT / ".env", override=True)
require_explicit_hil_arm(os.environ, confirmation=args.confirm)
suite = load_suite(args.suite)
token = os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN") or None
client_id = os.getenv("COGNITIVE_AGENT_CH1_HIL_CLIENT_ID") or None
cookie = os.getenv("COGNITIVE_AGENT_CH1_HIL_COOKIE") or None
api = JsonApiClient(args.base_url, cookie=cookie)
reader = passive_reader(token)
runner = Ch1HilRunner(
suite,
api,
reader,
confirmation=args.confirm,
approval_token=token,
hardware_client_id=client_id,
)
report = runner.run()
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(
json.dumps(report, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(json.dumps(report["summary"], ensure_ascii=False))
print(str(args.output.resolve()))
return 0 if report["summary"]["failed"] == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())