from __future__ import annotations

import asyncio
import hashlib
import hmac
import importlib.util
import json
import secrets
import socket
import threading
import time
import unicodedata
from collections.abc import Callable, Mapping
from datetime import UTC, datetime
from functools import wraps
from pathlib import Path
from typing import Any, Concatenate, Literal, Protocol, Self, cast

from bleak.exc import BleakError
from missioncore_plugin_sdk.v0alpha2 import (
    RuntimeActionInvocation,
    RuntimePluginDescriptor,
)
from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    SecretStr,
    ValidationError,
    field_validator,
    model_validator,
)

from k1link.artifacts import write_json_atomic
from k1link.compute.live_perception import LivePerceptionIngress
from k1link.device_plugins.xgrids_k1.ble.ap_activation import (
    device_ap_activation_session,
)
from k1link.device_plugins.xgrids_k1.ble.scanner import scan
from k1link.device_plugins.xgrids_k1.ble.wifi_provisioning import (
    AP_FALLBACK_IPV4,
    provision_wifi_once,
    read_wifi_status_once,
)
from k1link.device_plugins.xgrids_k1.calibration_snapshot import (
    DeviceCalibrationSnapshotReader,
    unavailable_device_calibration_snapshot,
)
from k1link.device_plugins.xgrids_k1.camera import (
    CAMERA_EXCLUSIVE_GROUP,
    CAMERA_SOURCE_LABELS,
    CAMERA_SOURCE_PATHS,
    CameraSourceId,
    CommittedCameraSegment,
    XgridsK1CameraGateway,
    build_xgrids_k1_camera_router,
)
from k1link.device_plugins.xgrids_k1.firmware_credential import (
    K1_FW302_CREDENTIAL_PROVIDER_ID,
)
from k1link.device_plugins.xgrids_k1.live_perception_shadow import (
    build_live_perception_shadow_router,
    ensure_live_shadow_token,
)
from k1link.device_plugins.xgrids_k1.mqtt import validate_private_ipv4
from k1link.device_plugins.xgrids_k1.mqtt.capture import seal_capture_clock
from k1link.device_plugins.xgrids_k1.protocol.application_authority import (
    MacOSKeychainApplicationAuthorityLoader,
)
from k1link.device_plugins.xgrids_k1.protocol.application_execution import (
    ApplicationAuthorityLoader,
    DormantApplicationControlCoordinator,
)
from k1link.device_plugins.xgrids_k1.protocol.application_mqtt import (
    ReviewedApplicationMqttTransport,
)
from k1link.device_plugins.xgrids_k1.protocol.application_publish import (
    UninstalledApplicationPublishSink,
    WriteDisabledOneShotPublisher,
)
from k1link.device_plugins.xgrids_k1.protocol.application_session import (
    InteractiveApplicationControlSession,
    OperatorPresenceConfirmation,
)
from k1link.device_plugins.xgrids_k1.protocol.modeling import observe_modeling_report
from k1link.device_plugins.xgrids_k1.protocol.modeling_safety import (
    LiveModelingControlSafety,
)
from k1link.device_plugins.xgrids_k1.protocol.normalizer import normalize_k1_message
from k1link.device_plugins.xgrids_k1.quick_connect_profile import (
    quick_connect_host_profile_id,
)
from k1link.device_plugins.xgrids_k1.viewer.messages import StreamMessage
from k1link.device_plugins.xgrids_k1.viewer.runtime import (
    VisualizationRuntime,
    new_live_session_dir,
)
from k1link.host_network import (
    HostWifiProfileError,
    associate_with_wifi_profile_once,
    ensure_wifi_profile_from_credential_source,
)
from k1link.sessions import ActiveSessionLease, resolve_missioncore_evidence_dir
from k1link.viewer.metrics import BridgeMetrics
from k1link.viewer.rerun_bridge import RerunSceneSettings
from k1link.web.device_lifecycle import (
    TERMINAL_ACQUISITION_STATES,
    AcquisitionRecord,
    OperationJournal,
    new_acquisition_id,
    new_device_id,
    new_device_session_id,
)
from k1link.web.plugin_runtime import (
    DevicePluginRuntimeContribution,
    InProcessDevicePluginRuntime,
    PluginActionNotFoundError,
    PluginExecutionError,
)

XGRIDS_K1_PLUGIN_ID = "nodedc.device.xgrids-lixelkity-k1"
XGRIDS_K1_PLUGIN_VERSION = "0.6.0"
XGRIDS_K1_MODEL_ID = "xgrids.lixelkity-k1"
XGRIDS_K1_COMPATIBILITY_PROFILE_ID = (
    "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2"
)

ConnectionMode = Literal["bridge", "quick-connect", "direct-connect"]
ConnectionTopology = Literal["direct-lan", "device-ap", "controller-hotspot"]
CONNECTION_TOPOLOGY_BY_MODE: dict[ConnectionMode, ConnectionTopology] = {
    "bridge": "direct-lan",
    "quick-connect": "device-ap",
    "direct-connect": "controller-hotspot",
}

ACTION_STATE_READ = "state.read"
ACTION_DISCOVERY_SCAN = "discovery.scan"
ACTION_DEVICE_INSPECT = "device.inspect"
ACTION_SENSOR_CATALOG_READ = "sensor.catalog.read"
ACTION_DEVICE_CALIBRATION_SNAPSHOT_READ = "calibration.device-snapshot.read"
ACTION_NETWORK_PROVISION = "network.provision"
ACTION_CONNECTION_VERIFY = "connection.verify"
ACTION_ACQUISITION_PREPARE = "acquisition.prepare"
ACTION_ACQUISITION_START = "acquisition.start"
ACTION_ACQUISITION_STOP = "acquisition.stop"
ACTION_ACQUISITION_ABORT = "acquisition.abort"


def _serialized_acquisition_access[**P, R](
    method: Callable[Concatenate[XgridsK1CompatibilityService, P], R],
) -> Callable[Concatenate[XgridsK1CompatibilityService, P], R]:
    """Serialize acquisition snapshots and producer lifecycle mutations.

    Runtime/camera calls may synchronously cause a state read, so the gate is
    deliberately re-entrant for the owning thread while excluding concurrent
    request handlers from observing a half-completed producer handoff.
    """

    @wraps(method)
    def serialized(
        service: XgridsK1CompatibilityService,
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> R:
        with service._acquisition_lifecycle_gate:  # noqa: SLF001
            return method(service, *args, **kwargs)

    return cast(
        "Callable[Concatenate[XgridsK1CompatibilityService, P], R]",
        serialized,
    )


ACTION_ACQUISITION_STATE_READ = "acquisition.state.read"
ACTION_STREAM_START_LIVE = "stream.start-live"
ACTION_STREAM_START_REPLAY = "stream.start-replay"
ACTION_STREAM_STOP = "stream.stop"
ACTION_CAMERA_PREVIEW_SELECT = "camera.preview.select"
ACTION_CAMERA_PREVIEW_STOP = "camera.preview.stop"
ACTION_VIEWER_SETTINGS_UPDATE = "viewer.settings.update"
ACTION_APPLICATION_CONTROL_SHADOW_STATE = "application-control.shadow-state"
ACTION_APPLICATION_CONTROL_SHADOW_ARM = "application-control.shadow-arm"
ACTION_APPLICATION_CONTROL_SHADOW_DISARM = "application-control.shadow-disarm"
ACTION_APPLICATION_CONTROL_SESSION_OPEN = "application-control.session.open"
ACTION_APPLICATION_CONTROL_WORKSPACE_ENTER = "application-control.workspace.enter"
ACTION_APPLICATION_CONTROL_SESSION_CLOSE = "application-control.session.close"

RequestedStreamId = Literal[
    "spatial.point-cloud.live",
    "spatial.pose.live",
    "device.modeling.live",
    "device.status.live",
    "device.heartbeat.live",
]
DEFAULT_LIVE_STREAMS: tuple[RequestedStreamId, ...] = (
    "spatial.point-cloud.live",
    "spatial.pose.live",
    "device.modeling.live",
    "device.status.live",
    "device.heartbeat.live",
)


class StrictRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")


class EmptyRequest(StrictRequest):
    pass


class CompatibilityProfileLoader(Protocol):
    def load_compatibility_profile(self, path: Path) -> dict[str, Any]: ...


class BleScanRequest(StrictRequest):
    duration_seconds: float = Field(default=6.0, ge=1.0, le=60.0)


class CompatibilityAttestationRequest(StrictRequest):
    """Selected profile whose facts must be verified from live DeviceInfo."""

    firmware_version: Literal["3.0.2"]
    topology: ConnectionTopology
    verification: Literal["live-device-info"]


class ConnectRequest(StrictRequest):
    device_id: str = Field(min_length=1, max_length=128)
    ssid: str | None = Field(default=None, min_length=1, max_length=128)
    password: SecretStr | None = Field(default=None, min_length=1, max_length=256)
    connection_mode: ConnectionMode = "bridge"
    compatibility_attestation: CompatibilityAttestationRequest
    operation_id: str | None = Field(default=None, min_length=1, max_length=128)
    idempotency_key: str | None = Field(default=None, min_length=1, max_length=160)

    @model_validator(mode="after")
    def validate_connection_topology(self) -> Self:
        expected = CONNECTION_TOPOLOGY_BY_MODE[self.connection_mode]
        if self.compatibility_attestation.topology != expected:
            raise ValueError(
                f"connection_mode={self.connection_mode} requires topology={expected}"
            )
        if self.connection_mode == "quick-connect":
            if self.ssid is not None or self.password is not None:
                raise ValueError(
                    "Quick Connect resolves its credential from the host Wi-Fi profile"
                )
            return self
        if self.ssid is None or self.password is None:
            raise ValueError("SSID and Wi-Fi password are required for this connection mode")
        if not 1 <= len(self.ssid.encode("utf-8")) <= 32:
            raise ValueError("SSID must contain between 1 and 32 UTF-8 bytes")
        if not 1 <= len(self.password.get_secret_value().encode("utf-8")) <= 64:
            raise ValueError(
                "Wi-Fi password must contain between 1 and 64 UTF-8 bytes"
            )
        return self


class LiveRequest(StrictRequest):
    project_name: str = Field(min_length=1, max_length=96)
    host: str | None = Field(default=None, max_length=15)
    # Omitted means run until the operator explicitly stops the acquisition.
    # A positive value remains available to compatibility clients that need a
    # bounded capture, but there is no application-level maximum.
    duration_seconds: float | None = Field(default=None, ge=1.0, allow_inf_nan=False)
    compatibility_attestation: CompatibilityAttestationRequest

    @field_validator("project_name")
    @classmethod
    def validate_project_name(cls, value: str) -> str:
        return normalize_project_name(value)


class OperationContextRequest(StrictRequest):
    operation_id: str | None = Field(default=None, min_length=1, max_length=128)
    idempotency_key: str | None = Field(default=None, min_length=1, max_length=160)
    deadline_seconds: float | None = Field(default=None, ge=1.0, le=86_400.0)


class OperatorPresenceRequest(StrictRequest):
    operator_present: Literal[True]
    owner_controlled_device: Literal[True]
    lixelgo_closed: Literal[True]
    battery_storage_confirmed: Literal[True]
    expected_physical_state_confirmed: Literal[True]

    def confirmation(self) -> OperatorPresenceConfirmation:
        # Build the wire-safety confirmation explicitly.  Subclasses add
        # transport metadata (for example ``timezone_name`` on control-session
        # open), which must never leak into this narrower dataclass contract.
        return OperatorPresenceConfirmation(
            operator_present=self.operator_present,
            owner_controlled_device=self.owner_controlled_device,
            lixelgo_closed=self.lixelgo_closed,
            battery_storage_confirmed=self.battery_storage_confirmed,
            expected_physical_state_confirmed=self.expected_physical_state_confirmed,
        )


class PrepareAcquisitionRequest(OperationContextRequest):
    project_name: str = Field(min_length=1, max_length=96)
    mount_type: Literal["handheld"] = "handheld"
    gnss_mode: Literal["none"] = "none"
    host: str | None = Field(default=None, max_length=15)
    duration_seconds: float | None = Field(default=None, ge=1.0, allow_inf_nan=False)
    requested_streams: tuple[RequestedStreamId, ...] = DEFAULT_LIVE_STREAMS
    evidence_policy: Literal["required", "best-effort", "disabled"] = "required"
    compatibility_attestation: CompatibilityAttestationRequest

    @field_validator("project_name")
    @classmethod
    def validate_project_name(cls, value: str) -> str:
        return normalize_project_name(value)


class StartAcquisitionRequest(OperationContextRequest):
    acquisition_id: str | None = Field(default=None, min_length=1, max_length=128)
    expected_state_revision: int | None = Field(default=None, ge=1)
    physical_acceptance: OperatorPresenceRequest | None = None


class StopAcquisitionRequest(OperationContextRequest):
    acquisition_id: str | None = Field(default=None, min_length=1, max_length=128)
    mode: Literal["graceful", "capture-only"] = "capture-only"
    operator_confirmed: bool = False
    physical_acceptance: OperatorPresenceRequest | None = None


class AbortAcquisitionRequest(OperationContextRequest):
    acquisition_id: str | None = Field(default=None, min_length=1, max_length=128)


class ReplayRequest(StrictRequest):
    path: str = Field(min_length=1, max_length=4096)
    speed: float = Field(default=1.0, ge=0.0, le=100.0)
    loop: bool = False


class CameraPreviewSelectRequest(StrictRequest):
    source_id: CameraSourceId
    device_session_id: str = Field(min_length=1, max_length=128)


class CameraPreviewStopRequest(StrictRequest):
    device_session_id: str = Field(min_length=1, max_length=128)
    generation: int = Field(ge=1)


class ViewerSettingsRequest(StrictRequest):
    point_size: float = Field(default=2.5, ge=0.5, le=12.0)
    color_mode: Literal["intensity", "height", "distance", "rgb", "class"] = "intensity"
    palette: Literal["turbo", "viridis", "plasma", "grayscale", "custom"] = "turbo"
    custom_color: str = Field(default="#f7f8f4", pattern=r"^#[0-9A-Fa-f]{6}$")
    accumulation_seconds: float = Field(default=12.0, ge=0.0, le=120.0)
    show_points: bool = True
    show_trajectory: bool = True
    show_grid: bool = True
    show_detections_2d: bool = False
    show_segmentation: bool = False
    show_cuboids_3d: bool = False


class ShadowApplicationControlArmRequest(StrictRequest):
    operator_confirmed: Literal[True]
    lease_seconds: float = Field(default=60.0, ge=15.0, le=300.0)
    timezone_name: str = Field(
        min_length=1,
        max_length=64,
        pattern=r"^[A-Za-z0-9._+-]+(?:/[A-Za-z0-9._+-]+)*$",
    )


class OpenApplicationControlSessionRequest(OperatorPresenceRequest):
    timezone_name: str = Field(
        min_length=1,
        max_length=64,
        pattern=r"^[A-Za-z0-9._+-]+(?:/[A-Za-z0-9._+-]+)*$",
    )


class EnterApplicationWorkspaceRequest(StrictRequest):
    operator_confirmed: Literal[True]


class XgridsK1CompatibilityService:
    """The proven K1 runtime kept intact behind the plugin facade."""

    def __init__(
        self,
        repository_root: Path,
        *,
        application_authority_loader: ApplicationAuthorityLoader | None = None,
        calibration_snapshot_reader: DeviceCalibrationSnapshotReader | None = None,
    ) -> None:
        self.repository_root = repository_root.resolve()
        self.evidence_root = resolve_missioncore_evidence_dir(self.repository_root)
        self._lock = threading.Lock()
        self._acquisition_lifecycle_gate = threading.RLock()
        self._provisioning_gate = threading.Lock()
        self._calibration_gate = threading.Lock()
        self._provisioning_active = False
        self._fingerprint_key = secrets.token_bytes(32)
        self._devices: list[dict[str, Any]] = []
        self._selected_device_id: str | None = None
        self._k1_ip: str | None = None
        self._connection_mode: ConnectionMode | None = None
        self._device_ids_by_transport_ref: dict[str, str] = {}
        self._device_id: str | None = None
        self._device_session_id: str | None = None
        self._device_session_opened_at: str | None = None
        self._connection_verification: dict[str, Any] = {
            "status": "not-probed",
            "endpoint_validation": "not-performed",
            "network_reachability": "unknown",
            "observed_at": None,
        }
        self._compatibility_attestation: dict[str, Any] | None = None
        self._operation_phase: str | None = None
        self._operation_message: str | None = None
        self._operations = OperationJournal()
        self._acquisition: AcquisitionRecord | None = None
        self._acquisition_project_name: str | None = None
        self._acquisition_mount_type: Literal["handheld"] | None = None
        self._acquisition_gnss_mode: Literal["none"] | None = None
        self._acquisition_out_dir: Path | None = None
        self._acquisition_start_operation_id: str | None = None
        self._acquisition_stop_operation_id: str | None = None
        self._acquisition_session_lease: ActiveSessionLease | None = None
        # The host-owned visual runtime receives the vendor normalizer
        # explicitly. There is no implicit K1 decoder in the visual layer.
        self._modeling_control_safety = LiveModelingControlSafety()
        self.live_perception_ingress = LivePerceptionIngress()
        (
            self.live_perception_token_path,
            self._live_perception_token,
        ) = ensure_live_shadow_token(self.repository_root)
        authority_loader = (
            application_authority_loader or MacOSKeychainApplicationAuthorityLoader()
        )
        self._calibration_snapshot_reader = (
            calibration_snapshot_reader
            or DeviceCalibrationSnapshotReader(
                authority_loader,
                compatibility_profile_id=XGRIDS_K1_COMPATIBILITY_PROFILE_ID,
            )
        )
        self._device_calibration = unavailable_device_calibration_snapshot(None)
        self._application_control = DormantApplicationControlCoordinator(
            authority_loader,
            WriteDisabledOneShotPublisher(UninstalledApplicationPublishSink()),
        )
        self._application_control_session = InteractiveApplicationControlSession(
            authority_loader,
            transport_factory=self._application_control_transport,
        )
        self.runtime = VisualizationRuntime(
            normalizer=normalize_k1_message,
            message_observer=self._observe_runtime_message,
        )
        self.camera_preview = XgridsK1CameraGateway(
            self.repository_root,
            XGRIDS_K1_PLUGIN_ID,
            committed_segment_observer=self._observe_committed_camera_segment,
        )

    def _application_control_transport(self, host: str) -> ReviewedApplicationMqttTransport:
        with self._lock:
            connection_mode = self._connection_mode
        return ReviewedApplicationMqttTransport(
            host,
            allow_device_ap=connection_mode == "quick-connect",
        )

    @_serialized_acquisition_access
    def state(self) -> dict[str, Any]:
        application_control = self._application_control.snapshot().as_dict()
        application_control_session = self._application_control_session.snapshot()
        runtime = self.runtime.snapshot()
        camera_preview = self.camera_preview.snapshot()
        self._reconcile_acquisition(runtime, camera_preview, application_control_session)
        runtime = self.runtime.snapshot()
        camera_preview = self.camera_preview.snapshot()
        metrics = runtime["metrics"]
        with self._lock:
            operation_phase = self._operation_phase
            operation_message = self._operation_message
            devices = list(self._devices)
            selected_device_id = self._selected_device_id
            k1_ip = self._k1_ip
            connection_mode = self._connection_mode
            device_id = self._device_id
            device_session_id = self._device_session_id
            device_session_opened_at = self._device_session_opened_at
            connection_verification = dict(self._connection_verification)
            compatibility_attestation = (
                dict(self._compatibility_attestation)
                if self._compatibility_attestation is not None
                else None
            )
            device_calibration = dict(self._device_calibration)
            acquisition = self._acquisition.as_dict() if self._acquisition is not None else None
            if acquisition is not None:
                acquisition["project_name"] = self._acquisition_project_name
                acquisition["mount_type"] = self._acquisition_mount_type
                acquisition["gnss_mode"] = self._acquisition_gnss_mode
                acquisition["cleanup_pending"] = (
                    self._acquisition_session_lease is not None
                    and acquisition["state"] in TERMINAL_ACQUISITION_STATES
                )

        runtime_active = runtime["source_mode"] != "idle" or runtime["phase"] in {
            "starting_live",
            "stopping",
            "error",
        }
        if operation_phase is not None:
            phase = operation_phase
            message = operation_message
        elif runtime_active:
            phase = runtime["phase"]
            message = runtime["message"]
        elif k1_ip is not None:
            phase = "connected"
            message = runtime["message"]
        elif selected_device_id is not None:
            phase = "device_selected"
            message = "Устройство выбрано. Теперь введите название и пароль Wi-Fi."
        elif devices:
            likely_count = sum(bool(item.get("likely_k1")) for item in devices)
            phase = "idle"
            message = (
                f"Найдено BLE-устройств: {len(devices)}. "
                f"Совместимых профилей: {likely_count}. Выберите нужное устройство."
            )
        else:
            phase = "idle"
            message = runtime["message"]

        operation_documents = self._operations.snapshot()
        active_profile_id = (
            XGRIDS_K1_COMPATIBILITY_PROFILE_ID if compatibility_attestation is not None else None
        )
        if device_calibration.get("status") != "available":
            device_calibration = unavailable_device_calibration_snapshot(active_profile_id)
        active_control = application_control_session["state"] not in {
            "idle",
            "completed",
            "closed",
            "failed",
        }
        return {
            "contract_version": "missioncore.device-plugin-state/v1alpha2",
            "phase": phase,
            "message": message,
            "devices": devices,
            "selected_device_id": selected_device_id,
            "k1_ip": k1_ip,
            "connection_mode": connection_mode,
            "compatibility": {
                "profile_id": active_profile_id,
                "decision": "limited" if active_profile_id is not None else "unknown",
                "permitted_mode": (
                    "active-control"
                    if active_profile_id is not None and active_control
                    else "read-only"
                    if active_profile_id is not None
                    else "evidence-only"
                ),
                "firmware_claim": (
                    "live-device-info-verification-required"
                    if compatibility_attestation is not None
                    else "exact-3.0.2-profile-not-selected"
                ),
                "attestation": compatibility_attestation,
                "vendor_writes_enabled": active_control,
                "camera_preview": (
                    (
                        "local-browser-adapter-available"
                        if camera_preview["runtime_dependency"]["status"] == "available"
                        else "local-runtime-dependency-missing"
                    )
                    if active_profile_id is not None
                    else "unverified"
                ),
            },
            "modeling_control_safety": self._modeling_control_safety.snapshot().as_dict(),
            "application_control_execution": application_control,
            "application_control_session": application_control_session,
            "device_ref": (
                {
                    "device_id": device_id,
                    "model_id": XGRIDS_K1_MODEL_ID,
                    "identity_stability": "provisional",
                    "identity_basis": "plugin-derived",
                    "transport_alias": selected_device_id,
                }
                if device_id is not None
                else None
            ),
            "device_session": (
                {
                    "device_session_id": device_session_id,
                    "device_id": device_id,
                    "opened_at": device_session_opened_at,
                    "compatibility_profile_id": active_profile_id,
                    "connectivity": "connected" if k1_ip is not None else "unknown",
                }
                if device_id is not None and device_session_id is not None
                else None
            ),
            "connection_verification": connection_verification,
            "sensor_catalog": _sensor_catalog(
                active_profile_id,
                device_session_id,
                camera_preview,
            ),
            "device_calibration": device_calibration,
            "camera_preview": camera_preview,
            "live_perception_shadow": self.live_perception_ingress.snapshot(),
            "acquisition": acquisition,
            "operations": operation_documents,
            "last_operation": operation_documents[-1] if operation_documents else None,
            "foxglove_ws_url": runtime["foxglove_ws_url"],
            "foxglove_viewer_url": runtime["foxglove_viewer_url"],
            "rerun_grpc_url": runtime["rerun_grpc_url"],
            "viewer_settings": runtime["viewer_settings"],
            "source_mode": runtime["source_mode"],
            "metrics": {
                "pipeline_ms": metrics["mqtt_to_publish_ms"],
                "end_to_end_ms": metrics["mqtt_to_publish_ms"],
                "decode_ms": metrics["decode_publish_ms"],
                "frame_rate": metrics["pcl_fps"],
                "frame_rate_hz": metrics["pcl_fps"],
                "point_count": metrics["last_point_count"],
                "dropped_preview_frames": metrics["preview_dropped"],
                "ai_end_to_end_ms": metrics.get("perception_end_to_end_ms"),
                "ai_end_to_end_p95_ms": metrics.get("perception_end_to_end_p95_ms"),
                "ai_frame_rate_hz": metrics.get("perception_fps"),
                "ai_dropped_frames": metrics.get("perception_dropped"),
                "ai_stale_ms": metrics.get("perception_stale_ms"),
                **metrics,
            },
        }

    async def scan_ble(self, duration_seconds: float) -> dict[str, Any]:
        operation, _ = self._operations.begin(
            ACTION_DISCOVERY_SCAN,
            deadline_seconds=duration_seconds + 10.0,
        )
        self._operations.transition(
            operation.operation_id,
            "running",
            stage_code="ble-discovery",
            message_code="discovery.scan.running",
        )
        self._set_operation("scanning", "Сканируем все устройства Bluetooth (BLE)…")
        try:
            result = await scan(duration_seconds)
            devices = [
                {
                    "device_id": item["macos_uuid"],
                    "name": item["local_name"] or item["name"],
                    "rssi": item["rssi"],
                    "address": None,
                    "connectable": True,
                    "likely_k1": item["k1_name_candidate"],
                }
                for item in result["devices"]
            ]
            with self._lock:
                self._devices = devices
                self._operation_message = f"Поиск завершён. Найдено BLE-устройств: {len(devices)}."
            self._operations.transition(
                operation.operation_id,
                "succeeded",
                stage_code="completed",
                message_code="discovery.scan.completed",
                result={"candidate_count": len(devices)},
            )
        except Exception as exc:
            self._operations.transition(
                operation.operation_id,
                "failed",
                stage_code="failed",
                message_code="discovery.scan.failed",
                error=_operation_error(exc, category="transport", side_effect_status="none"),
            )
            raise
        finally:
            with self._lock:
                self._operation_phase = None
        return self.state()

    async def connect(self, request: ConnectRequest) -> dict[str, Any]:
        control_state = self._application_control_session.snapshot()["state"]
        if control_state not in {"idle", "completed", "closed"}:
            raise RuntimeError(
                "сначала завершите или безопасно закройте текущую control-сессию K1"
            )
        known_ids = {str(item["device_id"]) for item in self.state()["devices"]}
        if request.device_id not in known_ids:
            raise ValueError("сначала найдите и выберите устройство через Bluetooth")
        quick_connect = request.connection_mode == "quick-connect"
        selected_device = next(
            item for item in self.state()["devices"] if item["device_id"] == request.device_id
        )
        selected_device_name = str(selected_device.get("name") or "").strip()
        if quick_connect and not selected_device_name:
            raise ValueError(
                "выбранный BLE-кандидат не сообщил имя точки доступа; "
                "Quick Connect остановлен без команды устройству"
            )
        quick_connect_profile_id = (
            quick_connect_host_profile_id(selected_device_name) if quick_connect else None
        )
        host_wifi_helper_path = (
            self.repository_root
            / "plugins"
            / "xgrids-k1"
            / "macos"
            / "associate_wifi.swift"
        )
        # Bridge and Direct Connect unwrap once at the BLE service boundary.
        # Quick Connect carries no browser/API credential. It first sends the
        # reviewed fixed AP-enable command, then the host-network adapter uses
        # the selected device's advertised name as its exact SSID and resolves
        # a device-scoped profile inside the OS credential store.
        password = (
            ""
            if request.password is None
            else request.password.get_secret_value()
        )
        request_fingerprint = self._request_fingerprint(
            ACTION_NETWORK_PROVISION,
            {
                "device_id": request.device_id,
                "ssid": request.ssid,
                "password": password if not quick_connect else None,
                "host_wifi_profile": (
                    quick_connect_profile_id if quick_connect else None
                ),
                "connection_mode": request.connection_mode,
                "compatibility_attestation": request.compatibility_attestation.model_dump(
                    mode="json"
                ),
            },
        )
        operation, created = self._operations.begin(
            ACTION_NETWORK_PROVISION,
            operation_id=request.operation_id,
            idempotency_key=request.idempotency_key,
            device_id=self._device_id,
            device_session_id=self._device_session_id,
            deadline_seconds=240.0,
            request_fingerprint=request_fingerprint,
        )
        if not created:
            password = ""
            return self.state()

        if not self._provisioning_gate.acquire(blocking=False):
            password = ""
            self._operations.transition(
                operation.operation_id,
                "failed",
                stage_code="busy",
                message_code="network.provision.already_running",
                error={
                    "category": "conflict",
                    "code": "provisioning-already-running",
                    "retryable": True,
                    "safe_to_retry": True,
                    "side_effect_status": "none",
                },
            )
            raise RuntimeError("другая операция настройки Wi-Fi уже выполняется")

        session_dir: Path | None = None
        network_change_attempted = False
        operation_stage = (
            "device-ap-activation" if quick_connect else "ble-provisioning-write"
        )
        try:
            if quick_connect:
                assert quick_connect_profile_id is not None
                operation_stage = "host-wifi-profile-preflight"
                self._set_operation(
                    "credential_preflight",
                    "Проверяем локальный профиль выбранного K1 до команды устройству.",
                )
                self._operations.transition(
                    operation.operation_id,
                    "running",
                    stage_code=operation_stage,
                    message_code="network.provision.host_wifi_profile_preflight",
                )
                profile_preflight = await asyncio.to_thread(
                    ensure_wifi_profile_from_credential_source,
                    host_wifi_helper_path,
                    quick_connect_profile_id,
                    selected_device_name,
                    K1_FW302_CREDENTIAL_PROVIDER_ID,
                    timeout_seconds=30.0,
                )
                if not profile_preflight["available"]:
                    raise HostWifiProfileError("credential-source-unavailable")

            with self._lock:
                active_acquisition = self._acquisition
                if (
                    active_acquisition is not None
                    and active_acquisition.state not in TERMINAL_ACQUISITION_STATES
                ):
                    raise RuntimeError(
                        "нельзя менять устройство или его сеть во время активной acquisition-сессии"
                    )
                self._provisioning_active = True
                # Once a new network operation is admitted, the previous route
                # and device session can no longer be represented as current.
                # This is especially important when Quick Connect may move the
                # host off the prior LAN before CoreWLAN reports an outcome.
                self._selected_device_id = None
                self._k1_ip = None
                self._connection_mode = None
                self._compatibility_attestation = None
                self._device_calibration = unavailable_device_calibration_snapshot(None)
                self._device_session_id = None
                self._device_session_opened_at = None
                self._connection_verification = {
                    "status": "not-probed",
                    "endpoint_validation": "not-performed",
                    "network_reachability": "unknown",
                    "observed_at": None,
                }

            # A connection-mode change can replace both the device session and
            # its address.
            self._application_control.disarm()
            # Revoke preview/producer state only after the active-acquisition
            # guard; a rejected network write must never stop evidence capture.
            self.camera_preview.stop_current()

            operation_message = (
                "Включаем точку доступа выбранного K1 и подключаем к ней управляющее устройство."
                if quick_connect
                else "Передаём устройству настройки Wi-Fi одним подтверждённым запросом."
            )
            self._set_operation("provisioning", operation_message)
            session_dir = _new_operation_session_dir(
                self.evidence_root,
                "viewer_k1_ap_association"
                if quick_connect
                else "viewer_wifi_provisioning",
            )
            session_dir.mkdir(parents=True, exist_ok=False)
            self._operations.transition(
                operation.operation_id,
                "running",
                stage_code=operation_stage,
                message_code="network.provision.running",
            )
            network_change_attempted = True
            if quick_connect:
                assert quick_connect_profile_id is not None
                operation_stage = "device-ap-activation"
                async with device_ap_activation_session(
                    request.device_id,
                    timeout_seconds=15.0,
                    write_mode="auto",
                ) as activation:
                    write_json_atomic(
                        session_dir / "ap-activation.redacted.json",
                        activation,
                    )
                    if not activation["ready_observed"]:
                        raise RuntimeError(
                            "K1 не подтвердил готовность точки доступа; "
                            "системное подключение Wi-Fi не запускалось"
                        )
                    operation_stage = "host-wifi-association"
                    self._operations.transition(
                        operation.operation_id,
                        "running",
                        stage_code=operation_stage,
                        message_code="network.provision.host_wifi_association",
                    )
                    # Keep the same BLE connection alive through the native
                    # CoreWLAN discovery/association handoff, as the reviewed
                    # LixelGO Quick Connect flow does.
                    try:
                        association = await asyncio.to_thread(
                            associate_with_wifi_profile_once,
                            host_wifi_helper_path,
                            quick_connect_profile_id,
                            selected_device_name,
                            scan_timeout_seconds=15.0,
                            timeout_seconds=180.0,
                        )
                    except HostWifiProfileError as exc:
                        write_json_atomic(
                            session_dir / "host-wifi-association.redacted.json",
                            {
                                "schema_version": 1,
                                "completed_at_utc": _utc_now_iso(),
                                "adapter": "CoreWLAN",
                                "outcome": "failed",
                                "reason_code": exc.reason_code,
                                "scan_attempt_count": exc.scan_attempt_count,
                                "scan_elapsed_ms": exc.scan_elapsed_ms,
                            },
                        )
                        raise
                completed_at = _utc_now_iso()
                ipv4: str | None = AP_FALLBACK_IPV4
                connection_manifest: dict[str, Any] = {
                    "schema_version": 1,
                    "started_at_utc": activation["started_at_utc"],
                    "completed_at_utc": completed_at,
                    "operation": "single_k1_ap_activation_and_host_profile_association",
                    "connection_mode": request.connection_mode,
                    "topology": request.compatibility_attestation.topology,
                    "outcome": association["outcome"],
                    "device_ap_activation_profile_id": activation["profile_id"],
                    "device_ap_activation_outcome": activation["outcome"],
                    "device_ap_ready_observed": activation["ready_observed"],
                    "device_ap_activation_write_performed": activation["write_performed"],
                    "device_ap_activation_write_mode": activation["write_mode"],
                    "host_wifi_adapter": association["adapter"],
                    "host_wifi_profile_id": quick_connect_profile_id,
                    "host_wifi_profile_ready_before_device_write": profile_preflight[
                        "available"
                    ],
                    "host_wifi_profile_preflight_adapter": profile_preflight["adapter"],
                    "host_wifi_profile_materialized_before_device_write": profile_preflight[
                        "profile_enrolled"
                    ],
                    "credential_provider_id": K1_FW302_CREDENTIAL_PROVIDER_ID,
                    "credential_provider_source": profile_preflight[
                        "credential_source"
                    ],
                    "host_wifi_profile_enrolled_now": association["profile_enrolled"],
                    "host_wifi_scan_attempt_count": association["scan_attempt_count"],
                    "host_wifi_scan_elapsed_ms": association["scan_elapsed_ms"],
                    "host_wifi_credential_source": association["credential_source"],
                    "device_ap_ssid_source": "selected-ble-advertised-name",
                    "credentials_resolved_by_plugin": (
                        profile_preflight["credential_source"]
                        == "exact-firmware-profile"
                    ),
                    "credentials_persisted_by_host_adapter": True,
                }
            else:
                if request.ssid is None:
                    raise RuntimeError("SSID отсутствует после проверки запроса")
                result = await provision_wifi_once(
                    request.device_id,
                    request.ssid,
                    password,
                    timeout_seconds=45.0,
                    write_mode="auto",
                )
                write_json_atomic(session_dir / "provisioning.sensitive.json", result)
                ipv4 = _provisioned_ipv4(result)
                connection_manifest = {
                    "schema_version": 1,
                    "started_at_utc": result["started_at_utc"],
                    "completed_at_utc": result["completed_at_utc"],
                    "operation": "single_reviewed_wifi_provisioning_write",
                    "profile_id": result["profile_id"],
                    "connection_mode": request.connection_mode,
                    "topology": request.compatibility_attestation.topology,
                    "outcome": result["outcome"],
                    "credentials_persisted_by_connector": False,
                }
            local_address_conflict = ipv4 is not None and _target_is_local_ipv4(ipv4)
            connection_manifest.update(
                {
                    "k1_lan_address_observed": ipv4 is not None,
                    "k1_lan_address_admitted": (
                        ipv4 is not None and not local_address_conflict
                    ),
                    "local_address_conflict": local_address_conflict,
                }
            )
            write_json_atomic(session_dir / "manifest.redacted.json", connection_manifest)
            if ipv4 is None:
                raise RuntimeError(
                    "Устройство не сообщило адрес в локальной сети; автоматического повтора не было"
                )
            if local_address_conflict:
                raise RuntimeError(
                    "Устройство сообщило IPv4-адрес, который уже принадлежит этому компьютеру; "
                    "адрес K1 не принят и автоматического повтора не было"
                )
            with self._lock:
                self._selected_device_id = request.device_id
                self._k1_ip = ipv4
                self._connection_mode = request.connection_mode
                self._device_id = self._device_ids_by_transport_ref.setdefault(
                    request.device_id,
                    new_device_id(),
                )
                self._device_session_id = new_device_session_id()
                self._device_session_opened_at = _utc_now_iso()
                self._compatibility_attestation = _attestation_snapshot(
                    request.compatibility_attestation
                )
                self._device_calibration = unavailable_device_calibration_snapshot(
                    XGRIDS_K1_COMPATIBILITY_PROFILE_ID
                )
                self._connection_verification = {
                    "status": "not-probed",
                    "endpoint_validation": "not-performed",
                    "network_reachability": "unknown",
                    "observed_at": None,
                }
                self._operation_message = {
                    "bridge": "K1 подключён к общей сети и сообщил локальный адрес.",
                    "direct-connect": (
                        "K1 подключён к хотспоту контроллера и сообщил локальный адрес."
                    ),
                    "quick-connect": (
                        "Mission Core подключён к точке доступа K1; адрес K1 подтверждён."
                    ),
                }[request.connection_mode]
            self._operations.transition(
                operation.operation_id,
                "succeeded",
                stage_code="connected",
                message_code="network.provision.completed",
                result={
                    "device_id": self._device_id,
                    "device_session_id": self._device_session_id,
                    "lan_address_observed": True,
                    "connection_mode": request.connection_mode,
                    "topology": request.compatibility_attestation.topology,
                    "address_source": (
                        "reviewed-k1-ap-baseline"
                        if quick_connect
                        else "ble-wifi-status"
                    ),
                },
                evidence_refs=(f"evidence-session-{session_dir.name}",),
            )
        except Exception as exc:
            self._operations.transition_if_pending(
                operation.operation_id,
                "failed",
                stage_code=f"{operation_stage}-failed",
                message_code="network.provision.failed",
                error=_operation_error(
                    exc,
                    category="transport" if quick_connect else "device",
                    side_effect_status=(
                        "unknown" if network_change_attempted else "none"
                    ),
                    safe_to_retry=not network_change_attempted,
                ),
                evidence_refs=(
                    (f"evidence-session-{session_dir.name}",) if session_dir is not None else ()
                ),
            )
            raise
        finally:
            password = ""
            with self._lock:
                self._provisioning_active = False
                self._operation_phase = None
            self._provisioning_gate.release()
        return self.state()

    def inspect_device(self) -> dict[str, Any]:
        """Return profile-scoped facts without probing or mutating the device."""

        return self.state()

    def _observe_runtime_message(
        self,
        message: StreamMessage,
        metrics: BridgeMetrics,
    ) -> bool:
        self._observe_live_perception_mqtt(message)
        if self._modeling_control_safety.observe(message, metrics):
            return True
        return observe_modeling_report(message, metrics)

    def _observe_live_perception_mqtt(self, message: StreamMessage) -> None:
        if message.source != "live_mqtt":
            return
        if message.topic.endswith("/lio_pcl") or message.topic == "RealtimePointcloud":
            modality: Literal["lidar", "pose"] = "lidar"
        elif message.topic.endswith("/lio_pose") or message.topic == "RealtimePath":
            modality = "pose"
        else:
            return
        self.live_perception_ingress.publish(
            modality=modality,
            source_id=message.topic,
            source_sequence=message.sequence,
            captured_at_epoch_ns=message.received_at_epoch_ns,
            received_monotonic_ns=(
                message.received_monotonic_ns
                if message.received_monotonic_ns is not None
                else time.monotonic_ns()
            ),
            payload=message.payload,
        )

    def _observe_committed_camera_segment(
        self,
        segment: CommittedCameraSegment,
    ) -> None:
        self.live_perception_ingress.publish(
            modality="camera-init" if segment.kind == "init" else "camera-frame",
            source_id=segment.source_id,
            source_sequence=segment.sequence,
            captured_at_epoch_ns=segment.host_epoch_ns,
            received_monotonic_ns=segment.host_monotonic_ns,
            payload=segment.payload,
        )

    def verify_connection(self) -> dict[str, Any]:
        """Refresh the session-scoped DHCP address from the read-only BLE status."""

        self._refresh_live_lan_address()
        return self.state()

    def _refresh_live_lan_address(self) -> str:
        with self._lock:
            selected_device_id = self._selected_device_id
            connection_mode = self._connection_mode
            acquisition = self._acquisition
            current_target = self._k1_ip
        if selected_device_id is None or connection_mode is None:
            raise ValueError("сначала выберите и подключите K1 через BLE/Wi-Fi")
        if acquisition is not None and acquisition.state not in TERMINAL_ACQUISITION_STATES:
            raise RuntimeError(
                "нельзя менять DHCP-привязку во время активной acquisition-сессии"
            )
        control_state = str(self._application_control_session.snapshot()["state"])
        if control_state not in {"idle", "completed", "closed", "failed"}:
            raise RuntimeError("нельзя менять DHCP-привязку при открытой control-сессии")
        if connection_mode == "quick-connect":
            if current_target != AP_FALLBACK_IPV4:
                raise RuntimeError("Quick Connect потерял фиксированный адрес точки доступа K1")
            return AP_FALLBACK_IPV4

        status_read = asyncio.run(
            read_wifi_status_once(selected_device_id, timeout_seconds=20.0)
        )
        observed_target = status_read["status"]["ipv4"]
        if observed_target is None or observed_target == AP_FALLBACK_IPV4:
            raise RuntimeError("K1 не сообщил актуальный DHCP-адрес через BLE status")
        target = validate_private_ipv4(observed_target)
        if _target_is_local_ipv4(target):
            raise RuntimeError("BLE status сообщил адрес, принадлежащий этому компьютеру")

        with self._lock:
            if (
                self._selected_device_id != selected_device_id
                or self._connection_mode != connection_mode
            ):
                raise RuntimeError("выбранное подключение K1 изменилось во время DHCP refresh")
            address_changed = self._k1_ip != target
            self._k1_ip = target
            if address_changed:
                self._device_session_id = new_device_session_id()
                self._device_session_opened_at = _utc_now_iso()
                self._device_calibration = unavailable_device_calibration_snapshot(
                    XGRIDS_K1_COMPATIBILITY_PROFILE_ID
                )
            self._connection_verification = {
                "status": "live-address-observed",
                "endpoint_validation": "ble-wifi-status-read",
                "network_reachability": "not-probed",
                "address_changed": address_changed,
                "previous_address_present": current_target is not None,
                "write_performed": False,
                "observed_at": status_read["observed_at_utc"],
            }
        return target

    def read_device_calibration_snapshot(self) -> dict[str, Any]:
        """Read and seal the two reviewed factory YAML files without device mutation."""

        if not self._calibration_gate.acquire(blocking=False):
            raise RuntimeError("чтение заводской калибровки уже выполняется")
        try:
            self._refresh_live_lan_address()
            with self._lock:
                target = self._k1_ip
                connection_mode = self._connection_mode
                attestation = self._compatibility_attestation
            if target is None or connection_mode is None:
                raise ValueError("у плагина нет подтверждённого локального адреса K1")
            if attestation is None:
                raise ValueError(
                    "для чтения калибровки сначала должен быть выбран точный профиль K1 FW 3.0.2"
                )
            snapshot = self._calibration_snapshot_reader.capture(
                host=target,
                evidence_root=self.evidence_root,
                allow_device_ap=connection_mode == "quick-connect",
            )
            with self._lock:
                if (
                    self._k1_ip != target
                    or self._connection_mode != connection_mode
                    or self._compatibility_attestation != attestation
                ):
                    raise RuntimeError(
                        "подключение K1 изменилось во время чтения; "
                        "снимок сохранён, но не активирован"
                    )
                self._device_calibration = dict(snapshot)
            return dict(snapshot)
        finally:
            self._calibration_gate.release()

    @_serialized_acquisition_access
    def open_application_control_session(
        self,
        request: OpenApplicationControlSessionRequest,
    ) -> dict[str, Any]:
        """Open one continuous canonical dialogue; no later stage is implied."""

        with self._lock:
            if self._provisioning_active:
                raise RuntimeError("нельзя открывать control-сессию во время настройки Wi-Fi")
        self._refresh_live_lan_address()
        with self._lock:
            target = self._k1_ip
            attestation = self._compatibility_attestation
            acquisition = self._acquisition
        if target is None or attestation is None:
            raise RuntimeError("сначала подключите K1 и выберите exact-profile")
        if acquisition is not None and acquisition.state not in TERMINAL_ACQUISITION_STATES:
            raise RuntimeError("control-сессия должна быть открыта до подготовки acquisition")
        self._application_control.disarm()
        self._application_control_session.open(
            host=validate_private_ipv4(target),
            timezone_name=request.timezone_name,
            confirmation=request.confirmation(),
        )
        return self.state()

    @_serialized_acquisition_access
    def enter_application_workspace(
        self,
        request: EnterApplicationWorkspaceRequest,
    ) -> dict[str, Any]:
        del request
        self._application_control_session.enter_workspace()
        return self.state()

    @_serialized_acquisition_access
    def close_application_control_session(self) -> dict[str, Any]:
        self._application_control_session.close_prestart()
        return self.state()

    @_serialized_acquisition_access
    def arm_application_control_shadow(
        self,
        request: ShadowApplicationControlArmRequest,
    ) -> dict[str, Any]:
        """Load a short authority lease without creating a publish path."""

        with self._lock:
            if self._provisioning_active:
                raise RuntimeError("нельзя вооружать shadow control во время настройки Wi-Fi")
            if self._k1_ip is None or self._selected_device_id is None:
                raise RuntimeError("сначала выберите и подключите K1 через BLE/Wi-Fi")
            if self._compatibility_attestation is None:
                raise RuntimeError("exact FW 3.0.2 local-network profile не выбран")
            acquisition = self._acquisition
            if acquisition is not None and acquisition.state not in TERMINAL_ACQUISITION_STATES:
                raise RuntimeError(
                    "нельзя вооружать shadow control во время активной acquisition-сессии"
                )
        if self.runtime.snapshot().get("source_mode") != "idle":
            raise RuntimeError("нельзя вооружать shadow control при активном live/replay source")

        self._application_control.arm(
            ttl_seconds=request.lease_seconds,
            epoch_seconds=int(time.time()),
            timezone_name=request.timezone_name,
        )
        return self.state()

    @_serialized_acquisition_access
    def disarm_application_control_shadow(self) -> dict[str, Any]:
        self._application_control.disarm()
        return self.state()

    @_serialized_acquisition_access
    def prepare_acquisition(self, request: PrepareAcquisitionRequest) -> dict[str, Any]:
        self._application_control.disarm()
        control_state = str(self._application_control_session.snapshot()["state"])
        if control_state == "failed":
            raise RuntimeError(
                "control-сессия K1 завершилась ошибкой и требует ручной проверки"
            )
        if control_state in {
            "connecting",
            "connection-ready",
            "workspace-requested",
            "project-requested",
            "project-ready",
            "start-requested",
            "initializing",
            "scanning",
            "stop-requested",
            "stopping",
            "awaiting-standby-confirmation",
        } and control_state != "workspace-ready":
            raise RuntimeError(
                "каноническая control-сессия должна ожидать сохранения проекта"
            )
        control_mode: Literal["operator-manual", "plugin-commanded"] = (
            "plugin-commanded" if control_state == "workspace-ready" else "operator-manual"
        )
        requested_streams = _validated_requested_streams(request)
        if request.host is None and control_mode == "operator-manual":
            self._refresh_live_lan_address()
        target = request.host or self.state()["k1_ip"]
        if not isinstance(target, str) or not target:
            raise ValueError(
                "сначала подключите устройство к Wi-Fi или укажите его локальный адрес"
            )
        target = validate_private_ipv4(target)
        with self._lock:
            connection_mode = self._connection_mode
        if connection_mode is None and request.compatibility_attestation.topology != "direct-lan":
            raise ValueError(
                "не-direct-LAN topology требует сначала завершить выбранный connection flow"
            )
        if connection_mode is not None:
            expected_topology = CONNECTION_TOPOLOGY_BY_MODE[connection_mode]
            if request.compatibility_attestation.topology != expected_topology:
                raise ValueError(
                    "compatibility attestation не совпадает с активным способом подключения"
                )
        if target == AP_FALLBACK_IPV4 and connection_mode != "quick-connect":
            raise ValueError(
                "адрес точки доступа K1 разрешён только после Quick Connect"
            )
        if connection_mode == "quick-connect" and target != AP_FALLBACK_IPV4:
            raise ValueError("Quick Connect должен использовать подтверждённый AP-адрес K1")
        if control_mode == "plugin-commanded":
            with self._lock:
                control_target = self._k1_ip
            if control_target is None or target != validate_private_ipv4(control_target):
                raise ValueError(
                    "локальный приём должен использовать тот же K1, что и control-сессия"
                )
        with self._lock:
            if self._acquisition_session_lease is not None:
                raise RuntimeError("предыдущая evidence-сессия ещё не остановлена и не запечатана")
        runtime_state = self.runtime.snapshot()
        if runtime_state.get("source_mode") != "idle":
            raise RuntimeError(
                "нельзя готовить acquisition во время активного live/replay источника; "
                "сначала остановите его"
            )
        device_id, device_session_id = self._ensure_device_context()
        request_fingerprint = self._request_fingerprint(
            ACTION_ACQUISITION_PREPARE,
            {
                "target": target,
                "duration_seconds": request.duration_seconds,
                "requested_streams": requested_streams,
                "evidence_policy": request.evidence_policy,
                "project_name": request.project_name,
                "mount_type": request.mount_type,
                "gnss_mode": request.gnss_mode,
                "compatibility_attestation": request.compatibility_attestation.model_dump(
                    mode="json"
                ),
            },
        )
        operation, created = self._operations.begin(
            ACTION_ACQUISITION_PREPARE,
            operation_id=request.operation_id,
            idempotency_key=request.idempotency_key,
            device_id=device_id,
            device_session_id=device_session_id,
            deadline_seconds=request.deadline_seconds or 30.0,
            cancellable=True,
            request_fingerprint=request_fingerprint,
        )
        if not created:
            return self.state()
        self._operations.transition(
            operation.operation_id,
            "running",
            stage_code="compatibility-check",
            message_code="acquisition.prepare.compatibility_check",
        )

        try:
            with self._lock:
                if self._provisioning_active:
                    raise RuntimeError(
                        "нельзя готовить acquisition во время настройки Wi-Fi устройства"
                    )
                if self._acquisition_session_lease is not None:
                    raise RuntimeError(
                        "предыдущая evidence-сессия ещё не остановлена и не запечатана"
                    )
                current = self._acquisition
                if current is not None and current.state not in TERMINAL_ACQUISITION_STATES:
                    raise RuntimeError(
                        "существующая acquisition-сессия ещё не завершена; сначала остановите её"
                    )
                acquisition = AcquisitionRecord(
                    acquisition_id=new_acquisition_id(),
                    device_id=device_id,
                    device_session_id=device_session_id,
                    compatibility_profile_id=XGRIDS_K1_COMPATIBILITY_PROFILE_ID,
                    control_mode=control_mode,
                    requested_streams=requested_streams,
                    target_host=target,
                    duration_seconds=request.duration_seconds,
                    evidence_policy=request.evidence_policy,
                )
                acquisition.transition(
                    "prepared",
                    message_code="acquisition.prepared",
                    result={
                        "write_operations_enabled": False,
                        "camera_preview": "observed-runtime-adapter-pending",
                    },
                )
                self._acquisition = acquisition
                self._acquisition_project_name = request.project_name
                self._acquisition_mount_type = request.mount_type
                self._acquisition_gnss_mode = request.gnss_mode
                self._acquisition_out_dir = new_live_session_dir(self.evidence_root)
                self._acquisition_start_operation_id = None
                self._acquisition_stop_operation_id = None
                self._compatibility_attestation = _attestation_snapshot(
                    request.compatibility_attestation
                )
            if control_mode == "plugin-commanded":
                self._application_control_session.open_project_prompt()
            self._operations.transition(
                operation.operation_id,
                "succeeded",
                stage_code="prepared",
                message_code="acquisition.prepare.completed",
                result={"acquisition_id": acquisition.acquisition_id},
            )
        except Exception as exc:
            self._operations.transition(
                operation.operation_id,
                "failed",
                stage_code="failed",
                message_code="acquisition.prepare.failed",
                error=_operation_error(exc, category="validation", side_effect_status="none"),
            )
            raise
        return self.state()

    @_serialized_acquisition_access
    def start_acquisition(self, request: StartAcquisitionRequest) -> dict[str, Any]:
        acquisition = self._require_acquisition(request.acquisition_id)
        plugin_commanded = acquisition.control_mode == "plugin-commanded"
        if plugin_commanded:
            if request.physical_acceptance is None:
                raise ValueError(
                    "START K1 требует явного подтверждения присутствия оператора"
                )
            if self._application_control_session.snapshot()["state"] != "project-ready":
                raise RuntimeError("канонический диалог K1 ещё не готов принять START")
        if (
            request.expected_state_revision is not None
            and request.expected_state_revision != acquisition.state_revision
        ):
            raise ValueError("состояние acquisition изменилось; обновите данные перед запуском")
        request_fingerprint = self._request_fingerprint(
            ACTION_ACQUISITION_START,
            {
                "acquisition_id": acquisition.acquisition_id,
                "expected_state_revision": request.expected_state_revision,
                "physical_acceptance": (
                    request.physical_acceptance.model_dump(mode="json")
                    if request.physical_acceptance is not None
                    else None
                ),
            },
        )
        operation, created = self._operations.begin(
            ACTION_ACQUISITION_START,
            operation_id=request.operation_id,
            idempotency_key=request.idempotency_key,
            device_id=acquisition.device_id,
            device_session_id=acquisition.device_session_id,
            deadline_seconds=request.deadline_seconds or 120.0,
            cancellable=True,
            request_fingerprint=request_fingerprint,
        )
        if not created:
            return self.state()
        self._operations.transition(
            operation.operation_id,
            "running",
            stage_code="arming-read-only-receiver",
            message_code="acquisition.start.arming_receiver",
        )

        owns_start = False
        cleanup_failed = False
        try:
            runtime_state = self.runtime.snapshot()
            with self._lock:
                if acquisition.state != "prepared":
                    raise RuntimeError(
                        f"acquisition нельзя запустить из состояния {acquisition.state}"
                    )
                if runtime_state.get("source_mode") != "idle":
                    raise RuntimeError(
                        "нельзя запускать acquisition поверх активного live/replay источника"
                    )
                out_dir = self._acquisition_out_dir
                project_name = self._acquisition_project_name
                if out_dir is None:
                    raise RuntimeError("для acquisition не выделена evidence-сессия")
                if project_name is None:
                    raise RuntimeError("для acquisition не задано название проекта")
                acquisition.transition(
                    "starting",
                    message_code="acquisition.start.waiting_receiver_ready",
                )
                self._acquisition_start_operation_id = operation.operation_id
                owns_start = True
            lease = ActiveSessionLease.acquire(out_dir.parent, out_dir)
            with self._lock:
                if self._acquisition_session_lease is not None:
                    lease.release()
                    raise RuntimeError("evidence-сессия уже удерживается активным acquisition")
                self._acquisition_session_lease = lease
            self.live_perception_ingress.begin_session(out_dir.name)
            self._modeling_control_safety.reset()
            self.runtime.start_live(
                acquisition.target_host,
                out_dir,
                duration_seconds=acquisition.duration_seconds,
                project_name=project_name,
            )
            self._arm_camera_recording(out_dir)
            if plugin_commanded:
                assert request.physical_acceptance is not None
                self._application_control_session.request_start(
                    project_name=project_name,
                    confirmation=request.physical_acceptance.confirmation(),
                )
        except Exception as exc:
            if owns_start:
                with self._lock:
                    if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                        acquisition.transition(
                            "stopping",
                            message_code="acquisition.start.cleanup",
                        )
                try:
                    self._stop_acquisition_sources(
                        camera_status="failed",
                        camera_failure_code="acquisition-start-failed",
                    )
                except Exception as cleanup_error:
                    cleanup_failed = True
                    exc.add_note(
                        f"acquisition start cleanup also failed: {type(cleanup_error).__name__}"
                    )
                with self._lock:
                    if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                        acquisition.transition("failed", message_code="acquisition.start.failed")
            self._operations.transition(
                operation.operation_id,
                "failed",
                stage_code="failed",
                message_code="acquisition.start.failed",
                error=_operation_error(
                    exc,
                    category="stream" if owns_start else "conflict",
                    side_effect_status="unknown" if cleanup_failed else "none",
                ),
            )
            raise
        return self.state()

    @_serialized_acquisition_access
    def stop_acquisition(self, request: StopAcquisitionRequest) -> dict[str, Any]:
        acquisition = self._require_acquisition(request.acquisition_id)
        plugin_commanded = acquisition.control_mode == "plugin-commanded"
        with self._lock:
            acquisition_state = acquisition.state
            expected_stop_operation_id = self._acquisition_stop_operation_id
            lease_retained = self._acquisition_session_lease is not None

        terminal_manual_stop_recovery = (
            request.operator_confirmed
            and not plugin_commanded
            and acquisition_state in TERMINAL_ACQUISITION_STATES
        )

        if request.operator_confirmed:
            if request.mode != "graceful":
                raise ValueError("operator_confirmed допустим только для graceful stop")
            if (
                acquisition_state != "awaiting_external_stop"
                and not terminal_manual_stop_recovery
            ):
                raise ValueError("acquisition не ожидает подтверждения физической остановки")
            if request.operation_id is None or request.operation_id != expected_stop_operation_id:
                raise ValueError(
                    "подтверждение остановки должно ссылаться на исходную stop-operation"
                )
            if plugin_commanded:
                raise ValueError(
                    "канонический STOP завершается автоматически по READY от K1"
                )
        elif (
            request.mode == "graceful"
            and acquisition_state
            not in {
                "acquiring",
                "awaiting_external_stop",
            }
            and not (
                plugin_commanded
                and acquisition_state in {"starting", "awaiting_external_start"}
                and self._application_control_session.snapshot()["state"] == "scanning"
            )
            and not (acquisition_state in TERMINAL_ACQUISITION_STATES and lease_retained)
        ):
            raise ValueError(
                "graceful stop допустим только после подтверждённого потока point cloud"
            )
        if (
            plugin_commanded
            and request.mode == "graceful"
            and not request.operator_confirmed
        ):
            if request.physical_acceptance is None:
                raise ValueError(
                    "STOP K1 требует явного подтверждения присутствия оператора"
                )
            if self._application_control_session.snapshot()["state"] != "scanning":
                raise RuntimeError("канонический диалог K1 ещё не готов принять STOP")

        if terminal_manual_stop_recovery:
            if lease_retained:
                self._stop_acquisition_sources(
                    camera_status="failed",
                    camera_failure_code="terminal-local-failure-after-device-stop",
                )
            return self.state()

        request_fingerprint = self._request_fingerprint(
            ACTION_ACQUISITION_STOP,
            {
                "acquisition_id": acquisition.acquisition_id,
                "mode": request.mode,
            },
        )
        operation, created = self._operations.begin(
            ACTION_ACQUISITION_STOP,
            operation_id=request.operation_id,
            idempotency_key=request.idempotency_key,
            device_id=acquisition.device_id,
            device_session_id=acquisition.device_session_id,
            deadline_seconds=request.deadline_seconds or 60.0,
            cancellable=False,
            request_fingerprint=request_fingerprint,
        )
        if (
            request.mode == "graceful"
            and not request.operator_confirmed
            and acquisition_state == "awaiting_external_stop"
        ):
            if operation.operation_id == expected_stop_operation_id:
                return self.state()
            self._operations.transition_if_pending(
                operation.operation_id,
                "failed",
                stage_code="conflict",
                message_code="acquisition.stop.already_awaiting_operator",
                error={
                    "category": "conflict",
                    "code": "graceful-stop-already-awaiting-operator",
                    "retryable": False,
                    "safe_to_retry": True,
                    "side_effect_status": "none",
                },
            )
            raise ValueError("acquisition уже ожидает подтверждения graceful stop")
        if not created and not request.operator_confirmed:
            return self.state()
        if acquisition.state in TERMINAL_ACQUISITION_STATES:
            if lease_retained:
                self._operations.transition_if_pending(
                    operation.operation_id,
                    "running",
                    stage_code="retrying-retained-cleanup",
                    message_code="acquisition.stop.retrying_retained_cleanup",
                )
                try:
                    completed = acquisition.state == "completed"
                    self._stop_acquisition_sources(
                        camera_status="complete" if completed else "failed",
                        camera_failure_code=(None if completed else "acquisition-cleanup-retry"),
                    )
                except Exception as exc:
                    self._operations.transition_if_pending(
                        operation.operation_id,
                        "failed",
                        stage_code="retained-cleanup-failed",
                        message_code="acquisition.stop.retained_cleanup_failed",
                        error=_operation_error(
                            exc,
                            category="stream",
                            side_effect_status="unknown",
                        ),
                    )
                    raise
                self._operations.transition_if_pending(
                    operation.operation_id,
                    "succeeded",
                    stage_code="retained-cleanup-completed",
                    message_code="acquisition.stop.retained_cleanup_completed",
                    result={
                        "acquisition_id": acquisition.acquisition_id,
                        "receiver_stopped": True,
                        "device_stop": "unknown",
                    },
                )
            else:
                self._operations.transition_if_pending(
                    operation.operation_id,
                    "succeeded",
                    stage_code="already-terminal",
                    message_code="acquisition.stop.already_terminal",
                    result={"acquisition_id": acquisition.acquisition_id},
                )
            return self.state()

        if request.mode == "graceful" and not request.operator_confirmed:
            if plugin_commanded:
                assert request.physical_acceptance is not None
                self.camera_preview.expect_source_end_for_device_stop()
                try:
                    self._application_control_session.request_stop(
                        confirmation=request.physical_acceptance.confirmation()
                    )
                except Exception:
                    self.camera_preview.cancel_expected_source_end()
                    raise
            with self._lock:
                acquisition.transition(
                    "awaiting_external_stop",
                    message_code=(
                        "acquisition.stop.device_stopping"
                        if plugin_commanded
                        else "acquisition.stop.operator_action_required"
                    ),
                    operator_instructions=()
                    if plugin_commanded
                    else (
                        "Дважды нажмите физическую кнопку устройства и "
                        "подтвердите остановку.",
                    ),
                )
                self._acquisition_stop_operation_id = operation.operation_id
            self._operations.transition(
                operation.operation_id,
                "running" if plugin_commanded else "operator_action_required",
                stage_code="awaiting-external-stop",
                message_code=(
                    "acquisition.stop.device_stopping"
                    if plugin_commanded
                    else "acquisition.stop.operator_action_required"
                ),
                result={"acquisition_id": acquisition.acquisition_id},
            )
            return self.state()

        self._operations.transition(
            operation.operation_id,
            "running",
            stage_code="stopping-receiver",
            message_code="acquisition.stop.stopping_receiver",
        )
        try:
            with self._lock:
                acquisition.transition("stopping", message_code="acquisition.stopping")
            self._stop_acquisition_sources(
                camera_status="complete",
                camera_failure_code=None,
            )
            self._cancel_pending_acquisition_operations(
                exclude_operation_id=operation.operation_id,
                reason_code="superseded-by-stop",
            )
            with self._lock:
                if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                    acquisition.transition("finalizing", message_code="acquisition.finalizing")
                    acquisition.transition(
                        "completed",
                        message_code="acquisition.completed",
                        result={
                            "receiver_stopped": True,
                            "device_stop": (
                                "operator-confirmed"
                                if request.mode == "graceful" and request.operator_confirmed
                                else "unknown"
                            ),
                        },
                    )
                if self._acquisition_stop_operation_id == operation.operation_id:
                    self._acquisition_stop_operation_id = None
            self._operations.transition(
                operation.operation_id,
                "succeeded",
                stage_code="completed",
                message_code="acquisition.stop.completed",
                result={
                    "acquisition_id": acquisition.acquisition_id,
                    "mode": request.mode,
                },
            )
        except Exception as exc:
            with self._lock:
                if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                    acquisition.transition("failed", message_code="acquisition.stop.failed")
            self._cancel_pending_acquisition_operations(
                exclude_operation_id=operation.operation_id,
                reason_code="acquisition-stop-failed",
            )
            self._operations.transition_if_pending(
                operation.operation_id,
                "failed",
                stage_code="failed",
                message_code="acquisition.stop.failed",
                error=_operation_error(exc, category="stream", side_effect_status="unknown"),
            )
            raise
        return self.state()

    @_serialized_acquisition_access
    def abort_acquisition(self, request: AbortAcquisitionRequest) -> dict[str, Any]:
        acquisition = self._require_acquisition(request.acquisition_id)
        request_fingerprint = self._request_fingerprint(
            ACTION_ACQUISITION_ABORT,
            {"acquisition_id": acquisition.acquisition_id},
        )
        operation, created = self._operations.begin(
            ACTION_ACQUISITION_ABORT,
            operation_id=request.operation_id,
            idempotency_key=request.idempotency_key,
            device_id=acquisition.device_id,
            device_session_id=acquisition.device_session_id,
            deadline_seconds=request.deadline_seconds or 30.0,
            request_fingerprint=request_fingerprint,
        )
        if not created:
            return self.state()
        try:
            if (
                acquisition.control_mode == "plugin-commanded"
                and acquisition.state == "prepared"
            ):
                self._application_control_session.close_prestart()
            with self._lock:
                should_abort = acquisition.state not in TERMINAL_ACQUISITION_STATES
                if should_abort:
                    acquisition.transition(
                        "stopping",
                        message_code="acquisition.aborting",
                    )
            if should_abort:
                self._stop_acquisition_sources(
                    camera_status="interrupted",
                    camera_failure_code="acquisition-aborted",
                )
                self._cancel_pending_acquisition_operations(
                    exclude_operation_id=operation.operation_id,
                    reason_code="superseded-by-abort",
                )
                with self._lock:
                    acquisition.transition(
                        "aborted",
                        message_code="acquisition.aborted",
                        result={"receiver_stopped": True, "device_state": "unknown"},
                    )
            self._operations.transition(
                operation.operation_id,
                "succeeded",
                stage_code="aborted",
                message_code="acquisition.abort.completed",
                result={"acquisition_id": acquisition.acquisition_id},
            )
        except Exception as exc:
            with self._lock:
                if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                    acquisition.transition(
                        "failed",
                        message_code="acquisition.abort.failed",
                        result={"receiver_stopped": False, "device_state": "unknown"},
                    )
            self._cancel_pending_acquisition_operations(
                exclude_operation_id=operation.operation_id,
                reason_code="acquisition-abort-failed",
            )
            self._operations.transition_if_pending(
                operation.operation_id,
                "failed",
                stage_code="failed",
                message_code="acquisition.abort.failed",
                error=_operation_error(exc, category="stream", side_effect_status="unknown"),
            )
            raise
        return self.state()

    def start_live(
        self,
        project_name: str,
        host: str | None,
        duration_seconds: float | None,
        compatibility_attestation: CompatibilityAttestationRequest,
    ) -> dict[str, Any]:
        """Deprecated compatibility shim over prepare + operator-manual start."""

        prepared = self.prepare_acquisition(
            PrepareAcquisitionRequest(
                project_name=project_name,
                host=host,
                duration_seconds=duration_seconds,
                compatibility_attestation=compatibility_attestation,
            )
        )
        acquisition = prepared.get("acquisition")
        if not isinstance(acquisition, dict) or not isinstance(
            acquisition.get("acquisition_id"), str
        ):
            raise RuntimeError("acquisition prepare did not return an acquisition id")
        return self.start_acquisition(
            StartAcquisitionRequest(acquisition_id=acquisition["acquisition_id"])
        )

    @_serialized_acquisition_access
    def start_replay(self, path: str, speed: float, loop: bool) -> dict[str, Any]:
        with self._lock:
            acquisition = self._acquisition
            lease_retained = self._acquisition_session_lease is not None
            if acquisition is not None and acquisition.state not in TERMINAL_ACQUISITION_STATES:
                raise RuntimeError(
                    "нельзя запускать replay во время активной acquisition-сессии; "
                    "сначала остановите или отмените её"
                )
            if lease_retained:
                raise RuntimeError(
                    "нельзя запускать replay: предыдущая evidence-сессия ещё не "
                    "остановлена и не запечатана"
                )
        replay_path = Path(path).expanduser().resolve()
        if not replay_path.is_relative_to(self.repository_root):
            raise ValueError("файл записи должен находиться внутри репозитория")
        self.runtime.start_replay(replay_path, speed=speed, loop=loop)
        return self.state()

    @_serialized_acquisition_access
    def stop(self) -> dict[str, Any]:
        """Deprecated capture-only shim; it never claims that K1 stopped scanning."""

        with self._lock:
            acquisition = self._acquisition
            lease_retained = self._acquisition_session_lease is not None
        if acquisition is None or acquisition.state in TERMINAL_ACQUISITION_STATES:
            if lease_retained:
                completed = acquisition is not None and acquisition.state == "completed"
                self._stop_acquisition_sources(
                    camera_status="complete" if completed else "failed",
                    camera_failure_code=(None if completed else "acquisition-cleanup-retry"),
                )
            else:
                self.camera_preview.stop_current()
                self.runtime.stop()
            return self.state()
        return self.stop_acquisition(
            StopAcquisitionRequest(
                acquisition_id=acquisition.acquisition_id,
                mode="capture-only",
            )
        )

    @_serialized_acquisition_access
    def select_camera_preview(self, request: CameraPreviewSelectRequest) -> dict[str, Any]:
        target = self._camera_target_for_session(request.device_session_id)
        with self._lock:
            acquisition = self._acquisition
            out_dir = self._acquisition_out_dir
            acquisition_active = (
                acquisition is not None and acquisition.state not in TERMINAL_ACQUISITION_STATES
            )
        if acquisition_active:
            if out_dir is None:
                raise RuntimeError("для acquisition не выделена evidence-сессия")
            self._arm_camera_recording(out_dir, require_session=True)
        self.camera_preview.select(request.source_id, target)
        return self.state()

    @_serialized_acquisition_access
    def stop_camera_preview(self, request: CameraPreviewStopRequest) -> dict[str, Any]:
        self._camera_target_for_session(request.device_session_id)
        self.camera_preview.stop(request.generation)
        return self.state()

    @_serialized_acquisition_access
    def close(self) -> None:
        self._application_control.close()
        self._application_control_session.close()
        with self._lock:
            acquisition = self._acquisition
            close_active_acquisition = (
                acquisition is not None and acquisition.state not in TERMINAL_ACQUISITION_STATES
            )
            if close_active_acquisition:
                assert acquisition is not None
                acquisition.transition(
                    "stopping",
                    message_code="acquisition.shutdown",
                )
        camera_error: Exception | None = None
        runtime_error: Exception | None = None
        terminal_error: Exception | None = None
        try:
            self.camera_preview.close()
        except Exception as exc:
            camera_error = exc
        try:
            self.runtime.close()
        except Exception as exc:
            runtime_error = exc
            if camera_error is not None:
                exc.add_note(
                    "camera gateway cleanup also failed: "
                    f"{type(camera_error).__name__}: {camera_error}"
                )
        if runtime_error is not None:
            terminal_error = runtime_error
        elif camera_error is not None:
            terminal_error = camera_error
        else:
            try:
                self._seal_acquisition_capture_clock()
            except Exception as exc:
                terminal_error = exc
        try:
            if close_active_acquisition:
                with self._lock:
                    assert acquisition is not None
                    if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                        acquisition.transition(
                            "failed" if terminal_error is not None else "interrupted",
                            message_code=(
                                "acquisition.shutdown.failed"
                                if terminal_error is not None
                                else "acquisition.shutdown.interrupted"
                            ),
                            result={
                                "receiver_stopped": terminal_error is None,
                                "device_state": "unknown",
                            },
                        )
                self._terminalize_acquisition_operations_on_shutdown(terminal_error)
        finally:
            with self._lock:
                out_dir = self._acquisition_out_dir
            if out_dir is not None:
                self.live_perception_ingress.end_session(out_dir.name)
            self.live_perception_ingress.close()
            if terminal_error is None:
                self._release_acquisition_session_lease()
        if terminal_error is not None:
            raise terminal_error

    def update_viewer_settings(self, request: ViewerSettingsRequest) -> dict[str, Any]:
        self.runtime.update_scene_settings(
            RerunSceneSettings(
                point_size=request.point_size,
                color_mode=request.color_mode,
                palette=request.palette,
                custom_color=request.custom_color,
                accumulation_seconds=request.accumulation_seconds,
                show_points=request.show_points,
                show_trajectory=request.show_trajectory,
                show_grid=request.show_grid,
                show_detections_2d=request.show_detections_2d,
                show_segmentation=request.show_segmentation,
                show_cuboids_3d=request.show_cuboids_3d,
            )
        )
        return self.state()

    def _arm_camera_recording(
        self,
        out_dir: Path,
        *,
        require_session: bool = False,
    ) -> None:
        camera = self.camera_preview.snapshot()
        if camera["recording"]["active"]:
            self.camera_preview.start_recording(out_dir)
            return
        if not out_dir.is_dir():
            if not require_session and camera["active_source_id"] is None:
                # The real runtime creates the session in its source thread. If
                # no camera has been selected yet, defer arming until selection
                # instead of racing that mkdir. No media can be lost yet.
                return
            deadline = time.monotonic() + 5.0
            while not out_dir.is_dir():
                runtime = self.runtime.snapshot()
                if runtime.get("phase") == "error":
                    raise RuntimeError(
                        "evidence-сессия не создана: live runtime завершился ошибкой"
                    )
                if time.monotonic() >= deadline:
                    raise RuntimeError(
                        "evidence-сессия не появилась вовремя для записи camera stream"
                    )
                time.sleep(0.01)
        self.camera_preview.start_recording(out_dir)

    def _stop_acquisition_sources(
        self,
        *,
        camera_status: Literal["complete", "interrupted", "failed"],
        camera_failure_code: str | None,
    ) -> None:
        with self._lock:
            out_dir = self._acquisition_out_dir
        camera_error: Exception | None = None
        runtime_error: Exception | None = None
        try:
            self.camera_preview.stop_recording(
                status=camera_status,
                failure_code=camera_failure_code,
            )
        except Exception as exc:
            camera_error = exc
        try:
            self.runtime.stop()
        except Exception as exc:
            runtime_error = exc
            if camera_error is not None:
                exc.add_note(
                    "camera archive cleanup also failed: "
                    f"{type(camera_error).__name__}: {camera_error}"
                )
        cleanup_complete = False
        try:
            if runtime_error is not None:
                raise runtime_error
            if camera_error is not None:
                raise camera_error
            self._seal_acquisition_capture_clock()
            cleanup_complete = True
        finally:
            if out_dir is not None:
                self.live_perception_ingress.end_session(out_dir.name)
            if cleanup_complete:
                self._release_acquisition_session_lease()

    def _seal_acquisition_capture_clock(self) -> None:
        with self._lock:
            out_dir = self._acquisition_out_dir
        # A prepared acquisition has reserved a future path but has no producer
        # or clock to seal. Once the live session exists, any missing or
        # inconsistent clock is a terminal evidence error.
        if out_dir is None or not out_dir.exists():
            return
        seal_capture_clock(out_dir / "captures" / "mqtt_live")

    def _release_acquisition_session_lease(self) -> None:
        with self._lock:
            lease = self._acquisition_session_lease
            self._acquisition_session_lease = None
        if lease is not None:
            lease.release()

    def _terminalize_acquisition_operations_on_shutdown(
        self,
        terminal_error: Exception | None,
    ) -> None:
        with self._lock:
            pending = (
                ("start", self._acquisition_start_operation_id),
                ("stop", self._acquisition_stop_operation_id),
            )
        for action_kind, operation_id in pending:
            if terminal_error is None:
                self._operations.transition_if_pending(
                    operation_id,
                    "cancelled",
                    stage_code="service-shutdown",
                    message_code=f"acquisition.{action_kind}.service_shutdown",
                )
            else:
                self._operations.transition_if_pending(
                    operation_id,
                    "failed",
                    stage_code="service-shutdown-failed",
                    message_code=f"acquisition.{action_kind}.service_shutdown_failed",
                    error=_operation_error(
                        terminal_error,
                        category="stream",
                        side_effect_status="unknown",
                    ),
                )
        with self._lock:
            self._acquisition_start_operation_id = None
            self._acquisition_stop_operation_id = None

    def _set_operation(self, phase: str, message: str) -> None:
        with self._lock:
            self._operation_phase = phase
            self._operation_message = message

    def _request_fingerprint(self, action: str, payload: Mapping[str, Any]) -> str:
        serialized = json.dumps(
            {"action": action, "payload": payload},
            ensure_ascii=False,
            sort_keys=True,
            separators=(",", ":"),
        ).encode("utf-8")
        return hmac.new(self._fingerprint_key, serialized, hashlib.sha256).hexdigest()

    def _cancel_pending_acquisition_operations(
        self,
        *,
        exclude_operation_id: str,
        reason_code: str,
    ) -> None:
        with self._lock:
            pending = (
                ("start", self._acquisition_start_operation_id),
                ("stop", self._acquisition_stop_operation_id),
            )
        for action_kind, operation_id in pending:
            if operation_id is None or operation_id == exclude_operation_id:
                continue
            self._operations.transition_if_pending(
                operation_id,
                "cancelled",
                stage_code=reason_code,
                message_code=f"acquisition.{action_kind}.{reason_code}",
            )
        with self._lock:
            if self._acquisition_start_operation_id != exclude_operation_id:
                self._acquisition_start_operation_id = None
            if self._acquisition_stop_operation_id != exclude_operation_id:
                self._acquisition_stop_operation_id = None

    def _ensure_device_context(self) -> tuple[str, str]:
        with self._lock:
            if self._device_id is None:
                self._device_id = new_device_id()
            if self._device_session_id is None:
                self._device_session_id = new_device_session_id()
                self._device_session_opened_at = _utc_now_iso()
            return self._device_id, self._device_session_id

    def _camera_target_for_session(self, device_session_id: str) -> str:
        with self._lock:
            current_session_id = self._device_session_id
            target = self._k1_ip
            attestation = self._compatibility_attestation
            connection_mode = self._connection_mode
        if current_session_id is None or device_session_id != current_session_id:
            raise ValueError("указана неактивная device-сессия")
        if attestation is None:
            raise ValueError("точный compatibility-профиль K1 не подтверждён")
        if target is None:
            raise ValueError("у плагина нет подтверждённого локального адреса K1")
        target = validate_private_ipv4(target)
        if target == AP_FALLBACK_IPV4 and connection_mode != "quick-connect":
            raise ValueError("адрес точки доступа K1 не принят для этой device-сессии")
        if connection_mode == "quick-connect" and target != AP_FALLBACK_IPV4:
            raise ValueError("Quick Connect camera target не совпадает с AP-адресом K1")
        return target

    def _require_acquisition(self, acquisition_id: str | None) -> AcquisitionRecord:
        with self._lock:
            acquisition = self._acquisition
            if acquisition is None:
                raise ValueError("acquisition ещё не подготовлена")
            if acquisition_id is not None and acquisition_id != acquisition.acquisition_id:
                raise ValueError("указана неизвестная acquisition-сессия")
            return acquisition

    def _reconcile_acquisition(
        self,
        runtime: Mapping[str, Any],
        camera: Mapping[str, Any],
        application_control_session: Mapping[str, Any],
    ) -> None:
        with self._lock:
            current = self._acquisition
            terminal_canonical_cleanup = (
                current is not None
                and current.state in TERMINAL_ACQUISITION_STATES
                and current.control_mode == "plugin-commanded"
                and application_control_session.get("state") == "completed"
                and self._acquisition_session_lease is not None
            )
            terminal_camera_status: Literal["complete", "failed"] = (
                "complete"
                if current is not None and current.state == "completed"
                else "failed"
            )
        if terminal_canonical_cleanup:
            self._stop_acquisition_sources(
                camera_status=terminal_camera_status,
                camera_failure_code=(
                    None
                    if terminal_camera_status == "complete"
                    else "terminal-local-failure-after-device-standby"
                ),
            )
            return

        completed_operation_id: str | None = None
        failed_operation_id: str | None = None
        failed_stop_operation_id: str | None = None
        unconfirmed_stop_operation_id: str | None = None
        completed_stop_operation_id: str | None = None
        receiver_ready_operation_id: str | None = None
        receiver_plugin_commanded = False
        acquisition_id: str | None = None
        camera_terminal_status: Literal["complete", "failed"] | None = None
        camera_failure_code: str | None = None
        stop_runtime_for_camera_failure = False
        stop_runtime_for_canonical_completion = False
        complete_after_seal = False
        completion_message_code = "acquisition.receiver_completed"
        completion_result: dict[str, Any] = {
            "receiver_stopped": True,
            "device_state": "unknown",
        }
        with self._lock:
            acquisition = self._acquisition
            if acquisition is None or acquisition.state in TERMINAL_ACQUISITION_STATES:
                return
            metrics = runtime.get("metrics")
            point_frames = metrics.get("pcl_frames", 0) if isinstance(metrics, dict) else 0
            phase = runtime.get("phase")
            source_mode = runtime.get("source_mode")
            source_ready = runtime.get("source_ready") is True
            camera_recording = camera.get("recording")
            camera_recording_active = (
                isinstance(camera_recording, dict) and camera_recording.get("active") is True
            )
            camera_error = camera.get("error")
            if camera.get("phase") == "error" and camera_recording_active:
                waiting_for_start = acquisition.state in {
                    "starting",
                    "awaiting_external_start",
                }
                waiting_for_stop = acquisition.state in {
                    "awaiting_external_stop",
                    "stopping",
                    "finalizing",
                }
                camera_failure_code = (
                    str(camera_error.get("code"))
                    if isinstance(camera_error, dict) and camera_error.get("code")
                    else "camera-producer-failed"
                )
                acquisition.transition(
                    "failed",
                    message_code="acquisition.camera_failed",
                    result={
                        "receiver_stopped": False,
                        "device_state": "unknown",
                        "camera_failure_code": camera_failure_code,
                    },
                )
                if waiting_for_start:
                    failed_operation_id = self._acquisition_start_operation_id
                if waiting_for_stop:
                    failed_stop_operation_id = self._acquisition_stop_operation_id
                camera_terminal_status = "failed"
                stop_runtime_for_camera_failure = True
            elif (
                acquisition.state == "awaiting_external_stop"
                and acquisition.control_mode == "plugin-commanded"
                and application_control_session.get("state") == "completed"
            ):
                acquisition.transition(
                    "finalizing",
                    message_code="acquisition.stop.device_standby_confirmed",
                )
                camera_terminal_status = "complete"
                stop_runtime_for_canonical_completion = True
                complete_after_seal = True
                completed_stop_operation_id = self._acquisition_stop_operation_id
                completion_message_code = "acquisition.stop.completed"
                completion_result = {
                    "receiver_stopped": True,
                    "device_state": "ready",
                    "device_stop": "protocol-confirmed",
                }
            elif acquisition.state in {"starting", "awaiting_external_start"} and point_frames > 0:
                acquisition.transition("acquiring", message_code="acquisition.acquiring")
                completed_operation_id = self._acquisition_start_operation_id
                acquisition_id = acquisition.acquisition_id
            elif acquisition.state == "starting" and source_ready:
                plugin_commanded = acquisition.control_mode == "plugin-commanded"
                receiver_plugin_commanded = plugin_commanded
                acquisition.transition(
                    "awaiting_external_start",
                    message_code=(
                        "acquisition.start.device_initializing"
                        if plugin_commanded
                        else "acquisition.start.operator_action_required"
                    ),
                    operator_instructions=(
                        (
                            "Приёмник готов. Ожидаем канонический START и калибровку K1; "
                            "не нажимайте физическую кнопку."
                            if plugin_commanded
                            else "Приёмник готов. Дважды нажмите физическую кнопку устройства."
                        ),
                    ),
                )
                receiver_ready_operation_id = self._acquisition_start_operation_id
                acquisition_id = acquisition.acquisition_id
            elif phase == "error":
                waiting_for_start = acquisition.state in {
                    "starting",
                    "awaiting_external_start",
                }
                waiting_for_stop = acquisition.state in {
                    "awaiting_external_stop",
                    "stopping",
                    "finalizing",
                }
                acquisition.transition("failed", message_code="acquisition.runtime_failed")
                camera_terminal_status = "failed"
                camera_failure_code = "runtime-failed"
                if waiting_for_start:
                    failed_operation_id = self._acquisition_start_operation_id
                if waiting_for_stop:
                    failed_stop_operation_id = self._acquisition_stop_operation_id
            elif (
                acquisition.state in {"starting", "awaiting_external_start"}
                and source_mode == "idle"
            ):
                acquisition.transition(
                    "failed",
                    message_code="acquisition.receiver_completed_without_point_data",
                    result={"receiver_stopped": True, "device_state": "unknown"},
                )
                camera_terminal_status = "failed"
                camera_failure_code = "receiver-completed-without-point-data"
                failed_operation_id = self._acquisition_start_operation_id
            elif acquisition.state == "acquiring" and source_mode == "idle":
                # Reserve terminal reconciliation before invoking camera or
                # filesystem callbacks. A nested/concurrent state read now
                # observes `finalizing` and cannot seal the same session twice.
                acquisition.transition(
                    "finalizing",
                    message_code="acquisition.finalizing_capture_clock",
                )
                camera_terminal_status = "complete"
                complete_after_seal = True
            elif acquisition.state == "awaiting_external_stop" and source_mode == "idle":
                acquisition.transition(
                    "failed",
                    message_code="acquisition.receiver_completed_before_stop_confirmation",
                    result={"receiver_stopped": True, "device_state": "unknown"},
                )
                camera_terminal_status = "failed"
                camera_failure_code = "receiver-completed-before-stop-confirmation"
                unconfirmed_stop_operation_id = self._acquisition_stop_operation_id

        reconciliation_error: Exception | None = None
        if camera_terminal_status is not None:
            terminal_error: Exception | None = None
            try:
                self.camera_preview.stop_recording(
                    status=camera_terminal_status,
                    failure_code=camera_failure_code,
                )
            except Exception as exc:
                terminal_error = exc
            if stop_runtime_for_camera_failure or stop_runtime_for_canonical_completion:
                try:
                    self.runtime.stop()
                except Exception as exc:
                    if terminal_error is not None:
                        exc.add_note(
                            "camera archive cleanup also failed: "
                            f"{type(terminal_error).__name__}: {terminal_error}"
                        )
                    terminal_error = exc
            try:
                if terminal_error is not None:
                    raise terminal_error
                self._seal_acquisition_capture_clock()
                if complete_after_seal:
                    with self._lock:
                        if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                            acquisition.transition(
                                "completed",
                                message_code=completion_message_code,
                                result=completion_result,
                            )
            except Exception as exc:
                reconciliation_error = exc
                if complete_after_seal:
                    with self._lock:
                        if acquisition.state not in TERMINAL_ACQUISITION_STATES:
                            acquisition.transition(
                                "failed",
                                message_code="acquisition.capture_clock_failed",
                            )
            finally:
                if reconciliation_error is None:
                    self._release_acquisition_session_lease()

        if receiver_ready_operation_id is not None:
            self._operations.transition_if_pending(
                receiver_ready_operation_id,
                "running" if receiver_plugin_commanded else "operator_action_required",
                stage_code=(
                    "device-initializing"
                    if receiver_plugin_commanded
                    else "awaiting-external-start"
                ),
                message_code=(
                    "acquisition.start.device_initializing"
                    if receiver_plugin_commanded
                    else "acquisition.start.operator_action_required"
                ),
                result={"acquisition_id": acquisition_id},
            )
        if completed_operation_id is not None:
            self._operations.transition_if_pending(
                completed_operation_id,
                "succeeded",
                stage_code="first-point-frame",
                message_code="acquisition.start.confirmed_by_data",
                result={"acquisition_id": acquisition_id, "confirmation": "point-frame"},
            )
        if failed_operation_id is not None:
            self._operations.transition_if_pending(
                failed_operation_id,
                "failed",
                stage_code="runtime-failed",
                message_code="acquisition.start.runtime_failed",
                error={
                    "category": "stream",
                    "code": "runtime-failed",
                    "retryable": False,
                    "safe_to_retry": True,
                    "side_effect_status": "none",
                },
            )
        if failed_stop_operation_id is not None:
            self._operations.transition_if_pending(
                failed_stop_operation_id,
                "failed",
                stage_code="runtime-failed",
                message_code="acquisition.stop.runtime_failed",
                error={
                    "category": "stream",
                    "code": "runtime-failed",
                    "retryable": False,
                    "safe_to_retry": False,
                    "side_effect_status": "unknown",
                },
            )
        if unconfirmed_stop_operation_id is not None:
            self._operations.transition_if_pending(
                unconfirmed_stop_operation_id,
                "failed",
                stage_code="receiver-completed-unconfirmed",
                message_code="acquisition.stop.receiver_completed_unconfirmed",
                error={
                    "category": "stream",
                    "code": "receiver-completed-before-device-stop-confirmation",
                    "retryable": False,
                    "safe_to_retry": False,
                    "side_effect_status": "unknown",
                },
            )
        if completed_stop_operation_id is not None:
            if reconciliation_error is None:
                self._operations.transition_if_pending(
                    completed_stop_operation_id,
                    "succeeded",
                    stage_code="device-standby-confirmed",
                    message_code="acquisition.stop.completed",
                    result={
                        "acquisition_id": acquisition.acquisition_id,
                        "confirmation": "device-status-ready-unbound",
                    },
                )
            else:
                self._operations.transition_if_pending(
                    completed_stop_operation_id,
                    "failed",
                    stage_code="local-finalization-failed",
                    message_code="acquisition.stop.local_finalization_failed",
                    error={
                        "category": "stream",
                        "code": "local-finalization-failed-after-device-standby",
                        "retryable": True,
                        "safe_to_retry": True,
                        "side_effect_status": "succeeded",
                    },
                )
        with self._lock:
            if self._acquisition_start_operation_id in {
                completed_operation_id,
                failed_operation_id,
            }:
                self._acquisition_start_operation_id = None
            if self._acquisition_stop_operation_id == failed_stop_operation_id:
                self._acquisition_stop_operation_id = None
            if self._acquisition_stop_operation_id == unconfirmed_stop_operation_id:
                self._acquisition_stop_operation_id = None
            if self._acquisition_stop_operation_id == completed_stop_operation_id:
                self._acquisition_stop_operation_id = None
        if reconciliation_error is not None:
            raise reconciliation_error


class XgridsK1ServicePort(Protocol):
    def state(self) -> dict[str, Any]: ...

    async def scan_ble(self, duration_seconds: float) -> dict[str, Any]: ...

    async def connect(self, request: ConnectRequest) -> dict[str, Any]: ...

    def inspect_device(self) -> dict[str, Any]: ...

    def verify_connection(self) -> dict[str, Any]: ...

    def read_device_calibration_snapshot(self) -> dict[str, Any]: ...

    def open_application_control_session(
        self,
        request: OpenApplicationControlSessionRequest,
    ) -> dict[str, Any]: ...

    def enter_application_workspace(
        self,
        request: EnterApplicationWorkspaceRequest,
    ) -> dict[str, Any]: ...

    def close_application_control_session(self) -> dict[str, Any]: ...

    def arm_application_control_shadow(
        self,
        request: ShadowApplicationControlArmRequest,
    ) -> dict[str, Any]: ...

    def disarm_application_control_shadow(self) -> dict[str, Any]: ...

    def prepare_acquisition(self, request: PrepareAcquisitionRequest) -> dict[str, Any]: ...

    def start_acquisition(self, request: StartAcquisitionRequest) -> dict[str, Any]: ...

    def stop_acquisition(self, request: StopAcquisitionRequest) -> dict[str, Any]: ...

    def abort_acquisition(self, request: AbortAcquisitionRequest) -> dict[str, Any]: ...

    def start_live(
        self,
        project_name: str,
        host: str | None,
        duration_seconds: float | None,
        compatibility_attestation: CompatibilityAttestationRequest,
    ) -> dict[str, Any]: ...

    def start_replay(self, path: str, speed: float, loop: bool) -> dict[str, Any]: ...

    def stop(self) -> dict[str, Any]: ...

    def select_camera_preview(self, request: CameraPreviewSelectRequest) -> dict[str, Any]: ...

    def stop_camera_preview(self, request: CameraPreviewStopRequest) -> dict[str, Any]: ...

    def update_viewer_settings(self, request: ViewerSettingsRequest) -> dict[str, Any]: ...


class XgridsK1PluginFacade:
    """Plugin action facade delegating to the proven compatibility service."""

    plugin_id = XGRIDS_K1_PLUGIN_ID
    action_ids = frozenset(
        {
            ACTION_STATE_READ,
            ACTION_DISCOVERY_SCAN,
            ACTION_DEVICE_INSPECT,
            ACTION_SENSOR_CATALOG_READ,
            ACTION_DEVICE_CALIBRATION_SNAPSHOT_READ,
            ACTION_NETWORK_PROVISION,
            ACTION_CONNECTION_VERIFY,
            ACTION_ACQUISITION_PREPARE,
            ACTION_ACQUISITION_START,
            ACTION_ACQUISITION_STOP,
            ACTION_ACQUISITION_ABORT,
            ACTION_ACQUISITION_STATE_READ,
            ACTION_STREAM_START_LIVE,
            ACTION_STREAM_START_REPLAY,
            ACTION_STREAM_STOP,
            ACTION_CAMERA_PREVIEW_SELECT,
            ACTION_CAMERA_PREVIEW_STOP,
            ACTION_VIEWER_SETTINGS_UPDATE,
            ACTION_APPLICATION_CONTROL_SHADOW_STATE,
            ACTION_APPLICATION_CONTROL_SHADOW_ARM,
            ACTION_APPLICATION_CONTROL_SHADOW_DISARM,
            ACTION_APPLICATION_CONTROL_SESSION_OPEN,
            ACTION_APPLICATION_CONTROL_WORKSPACE_ENTER,
            ACTION_APPLICATION_CONTROL_SESSION_CLOSE,
        }
    )

    def __init__(self, service: XgridsK1ServicePort) -> None:
        self.service = service

    async def invoke(self, invocation: RuntimeActionInvocation) -> dict[str, Any]:
        if invocation.plugin_id != self.plugin_id:
            raise PluginActionNotFoundError("runtime invocation targets another plugin")
        try:
            return await self._invoke_validated(
                invocation.action_id,
                invocation.parameters,
            )
        except (ValidationError, ValueError, PluginActionNotFoundError):
            raise
        except (BleakError, OSError, TimeoutError, RuntimeError) as exc:
            raise PluginExecutionError(str(exc)) from exc

    async def _invoke_validated(
        self,
        action_id: str,
        payload: Mapping[str, Any],
    ) -> dict[str, Any]:
        if action_id == ACTION_STATE_READ:
            EmptyRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.state)
        if action_id == ACTION_DISCOVERY_SCAN:
            scan_request = BleScanRequest.model_validate(payload)
            return await self.service.scan_ble(scan_request.duration_seconds)
        if action_id in {
            ACTION_DEVICE_INSPECT,
            ACTION_SENSOR_CATALOG_READ,
            ACTION_ACQUISITION_STATE_READ,
        }:
            EmptyRequest.model_validate(payload)
            if action_id == ACTION_DEVICE_INSPECT:
                return await asyncio.to_thread(self.service.inspect_device)
            return await asyncio.to_thread(self.service.state)
        if action_id == ACTION_DEVICE_CALIBRATION_SNAPSHOT_READ:
            EmptyRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.read_device_calibration_snapshot
            )
        if action_id == ACTION_NETWORK_PROVISION:
            connect_request = ConnectRequest.model_validate(payload)
            return await self.service.connect(connect_request)
        if action_id == ACTION_CONNECTION_VERIFY:
            EmptyRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.verify_connection)
        if action_id == ACTION_APPLICATION_CONTROL_SHADOW_STATE:
            EmptyRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.state)
        if action_id == ACTION_APPLICATION_CONTROL_SHADOW_ARM:
            arm_request = ShadowApplicationControlArmRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.arm_application_control_shadow,
                arm_request,
            )
        if action_id == ACTION_APPLICATION_CONTROL_SHADOW_DISARM:
            EmptyRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.disarm_application_control_shadow)
        if action_id == ACTION_APPLICATION_CONTROL_SESSION_OPEN:
            open_request = OpenApplicationControlSessionRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.open_application_control_session,
                open_request,
            )
        if action_id == ACTION_APPLICATION_CONTROL_WORKSPACE_ENTER:
            workspace_request = EnterApplicationWorkspaceRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.enter_application_workspace,
                workspace_request,
            )
        if action_id == ACTION_APPLICATION_CONTROL_SESSION_CLOSE:
            EmptyRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.close_application_control_session
            )
        if action_id == ACTION_ACQUISITION_PREPARE:
            prepare_request = PrepareAcquisitionRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.prepare_acquisition, prepare_request)
        if action_id == ACTION_ACQUISITION_START:
            start_request = StartAcquisitionRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.start_acquisition, start_request)
        if action_id == ACTION_ACQUISITION_STOP:
            stop_request = StopAcquisitionRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.stop_acquisition, stop_request)
        if action_id == ACTION_ACQUISITION_ABORT:
            abort_request = AbortAcquisitionRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.abort_acquisition, abort_request)
        if action_id == ACTION_STREAM_START_LIVE:
            live_request = LiveRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.start_live,
                live_request.project_name,
                live_request.host,
                live_request.duration_seconds,
                live_request.compatibility_attestation,
            )
        if action_id == ACTION_STREAM_START_REPLAY:
            replay_request = ReplayRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.start_replay,
                replay_request.path,
                replay_request.speed,
                replay_request.loop,
            )
        if action_id == ACTION_STREAM_STOP:
            EmptyRequest.model_validate(payload)
            return await asyncio.to_thread(self.service.stop)
        if action_id == ACTION_CAMERA_PREVIEW_SELECT:
            select_camera_request = CameraPreviewSelectRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.select_camera_preview,
                select_camera_request,
            )
        if action_id == ACTION_CAMERA_PREVIEW_STOP:
            stop_camera_request = CameraPreviewStopRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.stop_camera_preview,
                stop_camera_request,
            )
        if action_id == ACTION_VIEWER_SETTINGS_UPDATE:
            settings_request = ViewerSettingsRequest.model_validate(payload)
            return await asyncio.to_thread(
                self.service.update_viewer_settings,
                settings_request,
            )
        raise PluginActionNotFoundError(f"Unsupported XGRIDS K1 action: {action_id}")


def _utc_now_iso() -> str:
    return datetime.now(UTC).isoformat().replace("+00:00", "Z")


def normalize_project_name(value: str) -> str:
    """Return a bounded display name that is never interpreted as a path."""

    normalized = unicodedata.normalize("NFKC", value).strip()
    if not normalized:
        raise ValueError("название проекта должно содержать от 1 до 96 символов")
    try:
        normalized.encode("utf-8", errors="strict")
    except UnicodeEncodeError as exc:
        raise ValueError(
            "название проекта не должно содержать недопустимые Unicode-символы"
        ) from exc
    if len(normalized) > 96:
        raise ValueError("название проекта должно содержать не более 96 символов")
    if any(unicodedata.category(character) in {"Cc", "Cs"} for character in normalized):
        raise ValueError("название проекта не должно содержать управляющие символы")
    return normalized


def _operation_error(
    exc: Exception,
    *,
    category: str,
    side_effect_status: Literal["none", "possible", "confirmed", "unknown"],
    safe_to_retry: bool = False,
) -> dict[str, Any]:
    reason_code = getattr(exc, "reason_code", None)
    return {
        "category": category,
        "code": reason_code if isinstance(reason_code, str) and reason_code else type(exc).__name__,
        "retryable": False,
        "safe_to_retry": safe_to_retry,
        "side_effect_status": side_effect_status,
    }


def _validated_requested_streams(
    request: PrepareAcquisitionRequest,
) -> tuple[RequestedStreamId, ...]:
    if request.evidence_policy != "required":
        raise ValueError("текущий raw-first runtime поддерживает только evidence_policy=required")
    requested = tuple(request.requested_streams)
    if len(requested) != len(set(requested)) or set(requested) != set(DEFAULT_LIVE_STREAMS):
        raise ValueError(
            "текущий runtime поддерживает только полный проверенный набор live-потоков"
        )
    return DEFAULT_LIVE_STREAMS


def _attestation_snapshot(
    attestation: CompatibilityAttestationRequest,
) -> dict[str, Any]:
    return {
        "firmware_version": attestation.firmware_version,
        "topology": attestation.topology,
        "verification": attestation.verification,
        "basis": "selected-profile-live-device-info-required",
        "observed_at": _utc_now_iso(),
    }


def _sensor_catalog(
    active_profile_id: str | None,
    device_session_id: str | None,
    camera_preview: Mapping[str, Any],
) -> dict[str, Any]:
    camera_profile_active = active_profile_id is not None
    camera_phase = str(camera_preview.get("phase") or "idle")
    active_camera = camera_preview.get("active_source_id")
    delivery = camera_preview.get("delivery")
    dependency = camera_preview.get("runtime_dependency")
    dependency_available = (
        isinstance(dependency, Mapping) and dependency.get("status") == "available"
    )
    camera_streams: list[dict[str, Any]] = []
    for source_id in CAMERA_SOURCE_PATHS:
        side = "left" if source_id.endswith(".left") else "right"
        selected = source_id == active_camera
        if not camera_profile_active:
            availability = "unverified"
        elif not dependency_available:
            availability = "unavailable"
        elif selected and camera_phase == "streaming":
            availability = "streaming"
        elif selected and camera_phase == "error":
            availability = "error"
        elif selected:
            availability = "connecting"
        else:
            availability = "available"
        camera_streams.append(
            {
                "stream_id": f"camera.preview.{side}.live",
                "source_id": source_id,
                "semantic_channel_id": "camera.preview.live",
                "label": CAMERA_SOURCE_LABELS[source_id],
                "sensor_kind": "camera",
                "modality": "encoded-video",
                "availability": availability,
                "decode_status": (
                    "rtsp-h264-observed-browser-remux"
                    if camera_profile_active and dependency_available
                    else (
                        "local-runtime-dependency-missing"
                        if camera_profile_active
                        else "profile-not-selected"
                    )
                ),
                "endpoint_label": f"RTSP · {side}",
                "activation": {
                    "group_id": CAMERA_EXCLUSIVE_GROUP,
                    "max_active": 1,
                    "selected": selected,
                    "controllable": bool(
                        camera_profile_active and device_session_id and dependency_available
                    ),
                },
                "delivery": delivery if selected and isinstance(delivery, Mapping) else None,
                "frame_id": None,
                "coordinate_convention": "unresolved",
            }
        )
    return {
        "schema_version": "missioncore.sensor-catalog/v1alpha2",
        "revision": active_profile_id or "unprofiled-evidence-only",
        "streams": [
            {
                "stream_id": "spatial.point-cloud.live",
                "sensor_kind": "lidar",
                "modality": "point-cloud",
                "availability": "observed",
                "decode_status": "replay-and-physical-verified",
                "frame_id": "map",
                "coordinate_convention": "unverified",
            },
            {
                "stream_id": "spatial.pose.live",
                "sensor_kind": "pose-engine",
                "modality": "pose",
                "availability": "observed",
                "decode_status": "replay-and-physical-verified",
                "frame_id": "map",
                "coordinate_convention": "unverified",
            },
            {
                "stream_id": "device.modeling.live",
                "sensor_kind": "status",
                "modality": "acquisition-telemetry",
                "availability": "observed",
                "decode_status": "profile-decoded-physical-verified",
                "frame_id": None,
                "coordinate_convention": None,
            },
            {
                "stream_id": "device.status.live",
                "sensor_kind": "status",
                "modality": "device-status",
                "availability": "observed",
                "decode_status": "profile-decoded-physical-verified",
                "frame_id": None,
                "coordinate_convention": None,
            },
            {
                "stream_id": "device.heartbeat.live",
                "sensor_kind": "status",
                "modality": "health",
                "availability": "observed",
                "decode_status": "raw-only",
                "frame_id": None,
                "coordinate_convention": None,
            },
            *camera_streams,
        ],
    }


def _new_operation_session_dir(sessions_root: Path, suffix: str) -> Path:
    stamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
    base = sessions_root / f"{stamp}_{suffix}"
    candidate = base
    serial = 1
    while candidate.exists():
        serial += 1
        candidate = base.with_name(f"{base.name}_{serial:02d}")
    return candidate


def _provisioned_ipv4(result: Mapping[str, Any]) -> str | None:
    observations = result.get("observations")
    if not isinstance(observations, list):
        return None
    for observation in reversed(observations):
        if not isinstance(observation, dict):
            continue
        status = observation.get("status")
        if not isinstance(status, dict):
            continue
        address = status.get("ipv4")
        if isinstance(address, str) and address != AP_FALLBACK_IPV4:
            try:
                return validate_private_ipv4(address)
            except ValueError:
                continue
    return None


def _target_is_local_ipv4(target: str) -> bool:
    """Reject a BLE-reported target when the host route resolves back to itself.

    Connecting a UDP socket only asks the kernel to select a route and source
    address; it does not transmit a datagram. This keeps provisioning admission
    free of a speculative device probe while preventing a host address from
    being mistaken for the K1 endpoint.
    """

    try:
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as route_socket:
            route_socket.connect((validate_private_ipv4(target), 9))
            source_address = str(route_socket.getsockname()[0])
    except OSError as exc:
        raise RuntimeError("не удалось безопасно проверить маршрут к адресу K1") from exc
    return source_address == target


def _validate_installed_compatibility_profile(repository_root: Path) -> None:
    """Run the plugin-owned, fail-closed profile validator before activation."""

    plugin_root = repository_root.resolve() / "plugins" / "xgrids-k1"
    loader_path = plugin_root / "profile_loader.py"
    profile_path = plugin_root / "profiles" / "fw-3.0.2" / "local-network.v2.json"
    spec = importlib.util.spec_from_file_location(
        "missioncore_installed_xgrids_k1_profile_loader",
        loader_path,
    )
    if spec is None or spec.loader is None:
        raise RuntimeError("XGRIDS compatibility profile validator is unavailable")
    module = importlib.util.module_from_spec(spec)
    try:
        spec.loader.exec_module(module)
        loader = cast(CompatibilityProfileLoader, module)
        profile = loader.load_compatibility_profile(profile_path)
    except (AttributeError, OSError, ValueError) as exc:
        raise RuntimeError("XGRIDS compatibility profile failed validation") from exc
    if not isinstance(profile, dict) or profile.get("profile_id") != (
        XGRIDS_K1_COMPATIBILITY_PROFILE_ID
    ):
        raise RuntimeError("XGRIDS compatibility profile identity does not match the runtime")


def build_xgrids_k1_plugin(repository_root: Path) -> DevicePluginRuntimeContribution:
    """Manifest entrypoint for the reviewed XGRIDS compatibility adapter."""

    from k1link.device_plugins.xgrids_k1 import build_xgrids_k1_observation
    from k1link.device_plugins.xgrids_k1.legacy_api import build_xgrids_k1_legacy_router

    _validate_installed_compatibility_profile(repository_root)
    service = XgridsK1CompatibilityService(repository_root)
    adapter = XgridsK1PluginFacade(service)
    runtime = InProcessDevicePluginRuntime(
        adapter,
        RuntimePluginDescriptor(
            plugin_id=XGRIDS_K1_PLUGIN_ID,
            plugin_version=XGRIDS_K1_PLUGIN_VERSION,
            supported_host_api_versions=("missioncore.nodedc/v1alpha2",),
            action_ids=tuple(sorted(adapter.action_ids)),
        ),
        close=service.close,
    )
    return DevicePluginRuntimeContribution(
        runtime=runtime,
        legacy_routers=(
            build_xgrids_k1_legacy_router(runtime),
            build_xgrids_k1_camera_router(
                service.camera_preview,
                XGRIDS_K1_PLUGIN_ID,
            ),
            build_live_perception_shadow_router(
                service.live_perception_ingress,
                XGRIDS_K1_PLUGIN_ID,
                bearer_token=service._live_perception_token,
                result_receiver=service.runtime.publish_perception_result,
            ),
        ),
        observation=build_xgrids_k1_observation(repository_root),
    )
