feat(k1): complete canonical control lifecycle
This commit is contained in:
@@ -31,7 +31,7 @@ from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
|
||||
ATTESTATION = CompatibilityAttestationRequest(
|
||||
firmware_version="3.0.2",
|
||||
topology="direct-lan",
|
||||
operator_confirmed=True,
|
||||
verification="live-device-info",
|
||||
)
|
||||
PRIMARY_TEST_CREDENTIAL = "x" * 24
|
||||
SECONDARY_TEST_CREDENTIAL = "y" * 24
|
||||
@@ -120,12 +120,11 @@ class FakeInteractiveControlSession:
|
||||
self.state = "workspace-ready"
|
||||
self.start_projects: list[str] = []
|
||||
self.stop_calls = 0
|
||||
self.confirm_calls = 0
|
||||
|
||||
def snapshot(self) -> dict[str, object]:
|
||||
return {
|
||||
"state": self.state,
|
||||
"can_confirm_standby": self.state == "awaiting-standby-confirmation",
|
||||
"can_confirm_standby": False,
|
||||
}
|
||||
|
||||
def open_project_prompt(self) -> dict[str, object]:
|
||||
@@ -147,12 +146,6 @@ class FakeInteractiveControlSession:
|
||||
self.state = "awaiting-standby-confirmation"
|
||||
return self.snapshot()
|
||||
|
||||
def confirm_standby(self) -> dict[str, object]:
|
||||
assert self.state == "awaiting-standby-confirmation"
|
||||
self.confirm_calls += 1
|
||||
self.state = "completed"
|
||||
return self.snapshot()
|
||||
|
||||
def close_prestart(self) -> dict[str, object]:
|
||||
self.state = "closed"
|
||||
return self.snapshot()
|
||||
@@ -211,6 +204,8 @@ def test_prepare_creates_provisional_device_session_and_profiled_acquisition(
|
||||
assert state["device_ref"]["device_id"] != state["device_session"]["device_session_id"]
|
||||
assert state["acquisition"]["state"] == "prepared"
|
||||
assert state["acquisition"]["project_name"] == PROJECT_NAME
|
||||
assert state["acquisition"]["mount_type"] == "handheld"
|
||||
assert state["acquisition"]["gnss_mode"] == "none"
|
||||
assert state["acquisition"]["compatibility_profile_id"] == (XGRIDS_K1_COMPATIBILITY_PROFILE_ID)
|
||||
assert state["compatibility"]["vendor_writes_enabled"] is False
|
||||
|
||||
@@ -230,6 +225,29 @@ def test_project_name_is_normalized_and_control_characters_are_rejected() -> Non
|
||||
host="192.168.1.20",
|
||||
compatibility_attestation=ATTESTATION,
|
||||
)
|
||||
def test_only_physically_accepted_configuration_values_are_admitted() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
ConnectRequest(
|
||||
device_id="synthetic-device",
|
||||
ssid="synthetic-network",
|
||||
password=SecretStr(PRIMARY_TEST_CREDENTIAL),
|
||||
connection_mode="quick-connect", # type: ignore[arg-type]
|
||||
compatibility_attestation=ATTESTATION,
|
||||
)
|
||||
with pytest.raises(ValidationError):
|
||||
PrepareAcquisitionRequest(
|
||||
project_name=PROJECT_NAME,
|
||||
host="192.168.1.20",
|
||||
mount_type="uav", # type: ignore[arg-type]
|
||||
compatibility_attestation=ATTESTATION,
|
||||
)
|
||||
with pytest.raises(ValidationError):
|
||||
PrepareAcquisitionRequest(
|
||||
project_name=PROJECT_NAME,
|
||||
host="192.168.1.20",
|
||||
gnss_mode="rtk", # type: ignore[arg-type]
|
||||
compatibility_attestation=ATTESTATION,
|
||||
)
|
||||
|
||||
|
||||
def test_facade_arms_bounded_shadow_lease_without_installing_publish_transport(
|
||||
@@ -247,7 +265,8 @@ def test_facade_arms_bounded_shadow_lease_without_installing_publish_transport(
|
||||
service._compatibility_attestation = { # noqa: SLF001
|
||||
"firmware_version": "3.0.2",
|
||||
"topology": "direct-lan",
|
||||
"basis": "operator-attested",
|
||||
"verification": "live-device-info",
|
||||
"basis": "selected-profile-live-device-info-required",
|
||||
"observed_at": "2026-07-18T00:00:00Z",
|
||||
}
|
||||
|
||||
@@ -274,7 +293,7 @@ def test_facade_arms_bounded_shadow_lease_without_installing_publish_transport(
|
||||
assert disarmed["application_control_execution"]["lease"] is None
|
||||
|
||||
|
||||
def test_shadow_arm_requires_idle_connected_attested_device_before_keychain_read(
|
||||
def test_shadow_arm_requires_idle_connected_profile_selected_device_before_keychain_read(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
loader = FakeApplicationAuthorityLoader()
|
||||
@@ -405,21 +424,82 @@ def test_plugin_commanded_acquisition_keeps_start_and_stop_as_explicit_actions(
|
||||
physical_acceptance=PHYSICAL_ACCEPTANCE,
|
||||
)
|
||||
)
|
||||
stop_operation = stopping["last_operation"]
|
||||
assert control.stop_calls == 1
|
||||
assert stopping["acquisition"]["state"] == "awaiting_external_stop"
|
||||
assert stopping["last_operation"]["status"] == "running"
|
||||
|
||||
completed = service.stop_acquisition(
|
||||
control.state = "completed"
|
||||
completed = service.state()
|
||||
assert control.stop_calls == 1
|
||||
assert completed["acquisition"]["state"] == "completed"
|
||||
assert completed["acquisition"]["result"]["device_state"] == "ready"
|
||||
assert completed["last_operation"]["status"] == "succeeded"
|
||||
assert runtime.stop_calls == 1
|
||||
|
||||
|
||||
def test_device_standby_retires_sources_after_terminal_local_stop_failure(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service, runtime = service_with_fake_runtime(tmp_path)
|
||||
control = FakeInteractiveControlSession()
|
||||
service._application_control_session = control # type: ignore[assignment] # noqa: SLF001
|
||||
service._k1_ip = "192.168.1.20" # noqa: SLF001
|
||||
service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001
|
||||
|
||||
prepared = service.prepare_acquisition(
|
||||
PrepareAcquisitionRequest(
|
||||
project_name="TEST001",
|
||||
host="192.168.1.20",
|
||||
compatibility_attestation=ATTESTATION,
|
||||
)
|
||||
)
|
||||
acquisition_id = prepared["acquisition"]["acquisition_id"]
|
||||
service.start_acquisition(
|
||||
StartAcquisitionRequest(
|
||||
acquisition_id=acquisition_id,
|
||||
physical_acceptance=PHYSICAL_ACCEPTANCE,
|
||||
)
|
||||
)
|
||||
runtime.mark_ready()
|
||||
runtime.pcl_frames = 1
|
||||
service.state()
|
||||
|
||||
stopping = service.stop_acquisition(
|
||||
StopAcquisitionRequest(
|
||||
acquisition_id=acquisition_id,
|
||||
mode="graceful",
|
||||
operator_confirmed=True,
|
||||
operation_id=stop_operation["operation_id"],
|
||||
physical_acceptance=PHYSICAL_ACCEPTANCE,
|
||||
)
|
||||
)
|
||||
stop_operation = stopping["last_operation"]
|
||||
with service._lock: # noqa: SLF001
|
||||
assert service._acquisition is not None # noqa: SLF001
|
||||
service._acquisition.transition( # noqa: SLF001
|
||||
"failed",
|
||||
message_code="acquisition.camera_failed",
|
||||
result={"camera_failure_code": "camera-source-ended"},
|
||||
)
|
||||
service._operations.transition( # noqa: SLF001
|
||||
stop_operation["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",
|
||||
},
|
||||
)
|
||||
|
||||
control.state = "completed"
|
||||
recovered = service.state()
|
||||
|
||||
assert control.stop_calls == 1
|
||||
assert control.confirm_calls == 1
|
||||
assert completed["acquisition"]["state"] == "completed"
|
||||
assert control.state == "completed"
|
||||
assert recovered["acquisition"]["state"] == "failed"
|
||||
assert recovered["application_control_session"]["state"] == "completed"
|
||||
assert runtime.stop_calls == 1
|
||||
|
||||
|
||||
@@ -1257,7 +1337,7 @@ def test_prepare_rejects_stream_subsets_and_duplicates(
|
||||
)
|
||||
|
||||
|
||||
def test_exact_profile_is_inactive_until_explicit_operator_attestation(
|
||||
def test_exact_profile_is_inactive_until_selected_for_live_device_info_verification(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service, _ = service_with_fake_runtime(tmp_path)
|
||||
@@ -1267,7 +1347,7 @@ def test_exact_profile_is_inactive_until_explicit_operator_attestation(
|
||||
"profile_id": None,
|
||||
"decision": "unknown",
|
||||
"permitted_mode": "evidence-only",
|
||||
"firmware_claim": "exact-3.0.2-profile-not-attested",
|
||||
"firmware_claim": "exact-3.0.2-profile-not-selected",
|
||||
"attestation": None,
|
||||
"vendor_writes_enabled": False,
|
||||
"camera_preview": "unverified",
|
||||
@@ -1284,7 +1364,9 @@ def test_exact_profile_is_inactive_until_explicit_operator_attestation(
|
||||
|
||||
assert attested["compatibility"]["profile_id"] == XGRIDS_K1_COMPATIBILITY_PROFILE_ID
|
||||
assert attested["compatibility"]["decision"] == "limited"
|
||||
assert attested["compatibility"]["attestation"]["basis"] == "operator-attested"
|
||||
assert attested["compatibility"]["attestation"]["basis"] == (
|
||||
"selected-profile-live-device-info-required"
|
||||
)
|
||||
assert attested["device_session"]["compatibility_profile_id"] == (
|
||||
XGRIDS_K1_COMPATIBILITY_PROFILE_ID
|
||||
)
|
||||
@@ -1722,6 +1804,7 @@ def test_network_provisioning_is_single_flight_and_secret_is_unwrapped_only_at_b
|
||||
|
||||
assert boundary_calls == [("k1-a", "lab-network", PRIMARY_TEST_CREDENTIAL)]
|
||||
assert connected["k1_ip"] == "192.168.1.20"
|
||||
assert connected["connection_mode"] == "bridge"
|
||||
assert PRIMARY_TEST_CREDENTIAL not in str(connected)
|
||||
provision_operations = [
|
||||
item for item in connected["operations"] if item["action"] == "network.provision"
|
||||
@@ -1729,6 +1812,48 @@ def test_network_provisioning_is_single_flight_and_secret_is_unwrapped_only_at_b
|
||||
assert {item["status"] for item in provision_operations} == {"succeeded", "failed"}
|
||||
|
||||
|
||||
def test_network_provisioning_rejects_an_ipv4_owned_by_the_local_host(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service, _ = service_with_fake_runtime(tmp_path)
|
||||
service._devices = [{"device_id": "k1-a"}] # noqa: SLF001
|
||||
|
||||
async def fake_provision(*_: object, **__: object) -> dict[str, Any]:
|
||||
return {
|
||||
"started_at_utc": "2026-07-18T15:19:25Z",
|
||||
"completed_at_utc": "2026-07-18T15:19:26Z",
|
||||
"profile_id": "xgrids-k1-fw3-wifi-v1",
|
||||
"outcome": "lan_address_observed",
|
||||
"observations": [{"status": {"ipv4": "192.168.68.51"}}],
|
||||
}
|
||||
|
||||
monkeypatch.setattr(facade_module, "provision_wifi_once", fake_provision)
|
||||
monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: True)
|
||||
|
||||
with pytest.raises(RuntimeError, match="уже принадлежит этому компьютеру"):
|
||||
asyncio.run(
|
||||
service.connect(
|
||||
ConnectRequest(
|
||||
device_id="k1-a",
|
||||
ssid="lab-network",
|
||||
password=SecretStr(PRIMARY_TEST_CREDENTIAL),
|
||||
compatibility_attestation=ATTESTATION,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
state = service.state()
|
||||
assert state["selected_device_id"] is None
|
||||
assert state["k1_ip"] is None
|
||||
operation = next(
|
||||
item for item in state["operations"] if item["action"] == "network.provision"
|
||||
)
|
||||
assert operation["status"] == "failed"
|
||||
assert operation["error"]["safe_to_retry"] is False
|
||||
assert operation["error"]["side_effect_status"] == "unknown"
|
||||
|
||||
|
||||
def test_provisioning_cannot_switch_device_during_active_acquisition(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
|
||||
Reference in New Issue
Block a user