Стабилизация восстановления Bridge и Quick Connect
This commit is contained in:
@@ -202,6 +202,10 @@ def test_plugin_expected_state_preserves_its_non_gateway_http_status(
|
||||
"unable to perform operation on <TCPTransport closed=True>; "
|
||||
"the handler is closed"
|
||||
),
|
||||
RuntimeError(
|
||||
"Unexpected ASGI message 'websocket.send', after sending "
|
||||
"'websocket.close'."
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_device_plugin_events_treats_proven_transport_disconnect_as_completion(
|
||||
|
||||
@@ -31256,6 +31256,87 @@ def _persist_same_runtime_prepared_checkpoint_for_resolved_start(
|
||||
)
|
||||
|
||||
|
||||
def _persist_active_checkpoint_for_resolved_start(
|
||||
service: XgridsK1CompatibilityService,
|
||||
*,
|
||||
connection_mode: facade_module.ConnectionMode,
|
||||
target_ipv4: str,
|
||||
) -> None:
|
||||
"""Persist one proven active acquisition without process-local ownership."""
|
||||
|
||||
_persist_resolved_active_start_for_restart(
|
||||
service,
|
||||
connection_mode=connection_mode,
|
||||
target_ipv4=target_ipv4,
|
||||
)
|
||||
record = service._physical_command_ledger.snapshot().record # noqa: SLF001
|
||||
store = service._active_acquisition_checkpoint # noqa: SLF001
|
||||
assert record is not None and record.last_status is not None
|
||||
assert store is not None
|
||||
connection = record.connection
|
||||
binding = ActiveAcquisitionRecoveryTransportBinding(
|
||||
runtime_instance_id=service._snapshot_runtime_id, # noqa: SLF001
|
||||
intent_id=connection.intent_id,
|
||||
transport_ref=connection.transport_ref,
|
||||
connection_mode=connection.connection_mode,
|
||||
target_ipv4=connection.target_ipv4,
|
||||
target_port=connection.target_port,
|
||||
host_path_epoch=connection.host_path_epoch,
|
||||
control_session_id=connection.control_session_id,
|
||||
producer_generation=connection.producer_generation,
|
||||
logical_device_id="known-k1",
|
||||
compatibility_profile_id=XGRIDS_K1_COMPATIBILITY_PROFILE_ID,
|
||||
vendor_device_id_sha256=_RECOVERY_VENDOR_HASH,
|
||||
device_serial_sha256=_RECOVERY_SERIAL_HASH,
|
||||
)
|
||||
prepared = store.prepare(
|
||||
transition_id="prepare-active-recovery-start",
|
||||
predecessor_revision=0,
|
||||
acquisition_id=record.acquisition_id,
|
||||
original_start_operation_id=record.operation_id,
|
||||
start_payload_sha256=record.payload_sha256,
|
||||
identity=ActiveAcquisitionRecoveryIdentity(
|
||||
logical_device_id="known-k1",
|
||||
vendor_device_id_sha256=_RECOVERY_VENDOR_HASH,
|
||||
device_serial_sha256=_RECOVERY_SERIAL_HASH,
|
||||
),
|
||||
connection=ActiveAcquisitionRecoveryConnection(
|
||||
transport_ref=connection.transport_ref,
|
||||
connection_mode=connection.connection_mode,
|
||||
target_ipv4=connection.target_ipv4,
|
||||
target_port=connection.target_port,
|
||||
),
|
||||
compatibility_profile_id=XGRIDS_K1_COMPATIBILITY_PROFILE_ID,
|
||||
project_name="ACTIVE_RECOVERY",
|
||||
project_name_wire_sha256=active_acquisition_project_name_sha256(
|
||||
"ACTIVE_RECOVERY"
|
||||
),
|
||||
original_evidence_session_id="evidence-active-recovery",
|
||||
duration_seconds=None,
|
||||
requested_streams=("spatial.point-cloud.live", "camera.rgb.live"),
|
||||
evidence_policy="required",
|
||||
mount_type="handheld",
|
||||
gnss_mode="none",
|
||||
prepared_binding=binding,
|
||||
)
|
||||
store.activate(
|
||||
transition_id="activate-active-recovery-start",
|
||||
expected_revision=prepared.revision,
|
||||
expected_acquisition_id=prepared.acquisition_id,
|
||||
expected_start_operation_id=prepared.original_start_operation_id,
|
||||
status_proof=service._checkpoint_status_proof( # noqa: SLF001
|
||||
status=record.last_status,
|
||||
binding=binding,
|
||||
evidence_session_id="evidence-active-recovery",
|
||||
),
|
||||
physical_proof=service._checkpoint_physical_proof( # noqa: SLF001
|
||||
record=record,
|
||||
binding=binding,
|
||||
checkpoint=prepared,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _persist_resolved_unclassified_stop_for_restart(
|
||||
service: XgridsK1CompatibilityService,
|
||||
*,
|
||||
@@ -32930,6 +33011,125 @@ def test_restart_auto_settles_durable_prepared_resolved_start_standby(
|
||||
assert restarted_runtime.stop_calls == 0
|
||||
|
||||
|
||||
def test_new_bridge_ready_settles_untrusted_quick_checkpoint_without_command(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""A new exact READY closes stale Quick state locally in the same process."""
|
||||
|
||||
service, runtime = service_with_fake_runtime(tmp_path)
|
||||
_persist_active_checkpoint_for_resolved_start(
|
||||
service,
|
||||
connection_mode="quick-connect",
|
||||
target_ipv4="192.168.56.1",
|
||||
)
|
||||
ledger = service._physical_command_ledger # noqa: SLF001
|
||||
start = ledger.snapshot().record
|
||||
assert start is not None and start.last_status is not None
|
||||
stop_operation_id = "physical-stop-untrusted-quick-to-bridge"
|
||||
ledger.prepare(
|
||||
operation_id=stop_operation_id,
|
||||
parent_operation_id=start.operation_id,
|
||||
acquisition_id=start.acquisition_id,
|
||||
action="stop",
|
||||
identity=start.identity,
|
||||
connection=start.connection,
|
||||
compatibility_profile_id=start.compatibility_profile_id,
|
||||
payload_sha256="6" * 64,
|
||||
baseline_status=start.last_status,
|
||||
)
|
||||
ledger.mark_dispatching(stop_operation_id)
|
||||
ledger.mark_observing(
|
||||
stop_operation_id,
|
||||
publish_call_returned=True,
|
||||
packet_id=42,
|
||||
)
|
||||
ledger.mark_qos2_completed(stop_operation_id, packet_id=42)
|
||||
ledger.record_application_response(
|
||||
stop_operation_id,
|
||||
PhysicalCommandApplicationResponse(
|
||||
operation_id=stop_operation_id,
|
||||
action="stop",
|
||||
control_session_id=start.connection.control_session_id,
|
||||
host_path_epoch=start.connection.host_path_epoch,
|
||||
producer_generation=start.connection.producer_generation,
|
||||
result_code=PHYSICAL_COMMAND_APPLICATION_SUCCESS_CODE,
|
||||
success=True,
|
||||
payload_sha256="7" * 64,
|
||||
observed_at_utc="2026-08-09T12:00:03.000Z",
|
||||
),
|
||||
)
|
||||
service._mark_active_acquisition_checkpoint_untrusted( # noqa: SLF001
|
||||
trust="unavailable",
|
||||
reason_code="active-acquisition-recovery-checkpoint-error",
|
||||
)
|
||||
assert service._validate_active_acquisition_checkpoint_lineage() is None # noqa: SLF001
|
||||
|
||||
coordinator = service._physical_command_coordinator # noqa: SLF001
|
||||
coordinator.application_response(
|
||||
ApplicationMqttResponseEvidence(
|
||||
operation_key="bootstrap:bridge-ready:DeviceInfoRequest",
|
||||
response_topic="lixel/application/response/device_info",
|
||||
payload_sha256="8" * 64,
|
||||
modeling_action=None,
|
||||
result_code=None,
|
||||
success=None,
|
||||
observed_at_utc="2026-08-09T12:01:00.000Z",
|
||||
)
|
||||
)
|
||||
coordinator.bind_control_session(
|
||||
PhysicalCommandRuntimeBinding(
|
||||
vendor_device_id_sha256=_RECOVERY_VENDOR_HASH,
|
||||
device_serial_sha256=_RECOVERY_SERIAL_HASH,
|
||||
compatibility_profile_id=XGRIDS_K1_COMPATIBILITY_PROFILE_ID,
|
||||
intent_id="fresh-bridge-intent",
|
||||
transport_ref="test-ble-transport",
|
||||
connection_mode="bridge",
|
||||
target_ipv4="192.168.68.52",
|
||||
target_port=facade_module.CONTROL_MQTT_PORT,
|
||||
host_path_epoch=2,
|
||||
control_session_id="fresh-bridge-control",
|
||||
producer_generation=2,
|
||||
)
|
||||
)
|
||||
coordinator.device_status(
|
||||
ApplicationMqttDeviceStatusEvidence(
|
||||
vendor_device_id_sha256=_RECOVERY_VENDOR_HASH,
|
||||
device_serial_sha256=_RECOVERY_SERIAL_HASH,
|
||||
session_state="ready",
|
||||
session_state_code=MODELING_STATE_BASE + 300,
|
||||
project_bound=False,
|
||||
project_id_sha256=None,
|
||||
init_ready=False,
|
||||
status_message_sha256="9" * 64,
|
||||
mqtt_retained=False,
|
||||
observed_at_utc="2026-08-09T12:01:01.000Z",
|
||||
)
|
||||
)
|
||||
reconciliation_id = "reconcile-untrusted-quick-to-bridge-ready"
|
||||
standby_record = coordinator.reconcile_unresolved(
|
||||
reconciliation_id=reconciliation_id,
|
||||
)
|
||||
|
||||
settled = service._settle_untrusted_checkpoint_after_verified_standby( # noqa: SLF001
|
||||
reconciliation_id=reconciliation_id,
|
||||
reconciled_record=standby_record,
|
||||
)
|
||||
|
||||
checkpoint_store = service._active_acquisition_checkpoint # noqa: SLF001
|
||||
assert checkpoint_store is not None
|
||||
checkpoint = checkpoint_store.snapshot().checkpoint
|
||||
assert settled is True
|
||||
assert checkpoint is not None and checkpoint.state == "ceased"
|
||||
assert service._active_acquisition_checkpoint_trust == "trusted" # noqa: SLF001
|
||||
assert service._active_acquisition_checkpoint_reason is None # noqa: SLF001
|
||||
assert standby_record["connection"]["connection_mode"] == "quick-connect"
|
||||
assert standby_record["reconciliations"][-1]["verified_binding"]["connection"][
|
||||
"connection_mode"
|
||||
] == "bridge"
|
||||
assert runtime.start_calls == []
|
||||
assert runtime.stop_calls == 0
|
||||
|
||||
|
||||
def test_transition_invalid_fences_repeated_verify_until_service_restart(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user