Retain K1 connection between scans and admit the next named acquisition

This commit is contained in:
DCCONSTRUCTIONS
2026-09-07 21:13:17 +03:00
parent ed9a77ba73
commit c6693d6f44
17 changed files with 586 additions and 274 deletions
+20 -2
View File
@@ -3814,6 +3814,18 @@ class XgridsK1CompatibilityService:
expected_session_generation: int | None = None,
expected_state_revision: int | None = None,
) -> None:
current = self._application_control_session.snapshot()
if current.get("state") == "completed" and current.get("control_socket_open") is True:
self._application_control_session.close_prestart(
expected_session_generation=expected_session_generation,
expected_state_revision=expected_state_revision,
)
self._application_control_session.close()
retired = self._application_control_session.snapshot()
if retired.get("session_generation") != current.get("session_generation"):
raise ApplicationAcceptanceError("control session changed during retirement")
expected_session_generation = retired.get("session_generation")
expected_state_revision = retired.get("state_revision")
self._application_control_session.retire_for_network_change(
allow_terminal_failure=allow_terminal_failure,
expected_session_generation=expected_session_generation,
@@ -27534,7 +27546,10 @@ class XgridsK1CompatibilityService:
}
supervisor = self._connection_supervisor.snapshot()
if (
control_state in active_control_states
(control_state in active_control_states or (
control_state == "completed"
and application_control_session.get("control_socket_open") is True
))
and isinstance(verified_control, Mapping)
and supervisor.intent is not None
and supervisor.endpoint.target is not None
@@ -27703,7 +27718,10 @@ class XgridsK1CompatibilityService:
supervisor = self._connection_supervisor.snapshot()
if (
control_state in {"idle", "completed", "closed", "failed"}
(control_state in {"idle", "closed", "failed"} or (
control_state == "completed"
and application_control_session.get("control_socket_open") is not True
))
and supervisor.control_plane.state == "healthy"
and supervisor.control_plane.session_id is not None
and supervisor.intent is not None
@@ -241,12 +241,17 @@ class NodeK1Sensor:
control = state.get("application_control_session") or {}
phase = control.get("state")
physical = control.get("physical_command") or state.get("physical_command") or {}
if ("acquisition.start" in dispatched
and phase in {"start-requested", "initializing", "scanning"}):
# The just-admitted START owns its pending physical edge.
# Returning its state does not dispatch or reconcile it again.
return project_sensor(state, node_id)
if physical.get("requires_reconciliation"):
raise ValueError("Physical state requires explicit reconciliation")
acquisition = state.get("acquisition") or {}
payload = {"expected_snapshot_runtime_id": runtime}
next_action = None
if phase == "connection-ready":
if phase in {"connection-ready", "completed"}:
if control.get("inspection_only"):
next_action = "application-control.session.open"
payload.update(acceptance, timezone_name="UTC")
@@ -276,7 +281,7 @@ class NodeK1Sensor:
expected_state_revision=acquisition["state_revision"],
physical_acceptance=acceptance,
)
elif phase in {"failed", "idle", "closed", "completed"}:
elif phase in {"failed", "idle", "closed"}:
raise ValueError("K1 control not ready")
elif phase in {"start-requested", "initializing", "scanning"}:
return project_sensor(state, node_id)
@@ -285,6 +285,7 @@ class PhysicalAcceptanceDialogueExecutor:
self._active_authority: ApplicationControlAuthority | None = None
self._active_binding: LiveDeviceControlBinding | None = None
self._prepared_binding: LiveDeviceControlBinding | None = None
self._standby_binding: LiveDeviceControlBinding | None = None
self._checkpoint_owner = object()
self._issued_checkpoint: str | None = None
self._start_permit_snapshot: dict[str, object] | None = None
@@ -801,9 +802,23 @@ class PhysicalAcceptanceDialogueExecutor:
allowed_response_topics={MODELING_STATUS_RESPONSE_TOPIC},
)
self._dialogue_stage = "standby-confirmed"
self._standby_binding = binding
self._active_authority = None
self._active_binding = None
def maintain_standby_until_next_acquisition(self, requested: Callable[[], bool]) -> None:
"""Pump the retained socket after READY; issue no device commands."""
if self._dialogue_stage != "standby-confirmed" or self._standby_binding is None:
raise ApplicationAcceptanceError("next acquisition requires confirmed standby")
while True:
self._transport.maintain_open_for(
CONTROL_NETWORK_PUMP_QUANTUM_SECONDS,
allowed_response_topics={MODELING_STATUS_RESPONSE_TOPIC},
)
self._transport.validate_bound_status(self._standby_binding)
if requested() and self._transport.pre_start_ready(self._standby_binding):
return
def snapshot(self) -> dict[str, object]:
return {
"mode": "physical-acceptance-only",
@@ -170,8 +170,11 @@ class OperatorPresenceConfirmation:
class InteractiveApplicationControlSession:
"""Own one canonical K1 MQTT dialogue across explicit operator UI events.
"""Own canonical acquisition dialogues and retain control between scans.
Each explicit new scan gets a fresh one-shot MQTT dialogue on the existing
device/network binding. Completed scans keep pumping their idle connection
until that request or explicit local retirement; no Wi-Fi setup is repeated.
Only this background thread touches the MQTT client. UI requests merely
release one named checkpoint. No checkpoint is advanced by elapsed time,
and neither START nor STOP has an automatic retry path.
@@ -361,7 +364,7 @@ class InteractiveApplicationControlSession:
expected_session_generation=expected_session_generation,
expected_state_revision=expected_state_revision,
)
self._require_phase_locked("connection-ready")
self._require_workspace_entry_locked()
if self._inspection_only and not self._inspection_promotion_allowed:
raise ApplicationAcceptanceError(
"read-only inspection has not completed its Verify boundary"
@@ -376,7 +379,7 @@ class InteractiveApplicationControlSession:
expected_session_generation=expected_session_generation,
expected_state_revision=expected_state_revision,
)
self._require_phase_locked("connection-ready")
self._require_workspace_entry_locked()
if self._inspection_only and not self._inspection_promotion_allowed:
raise ApplicationAcceptanceError(
"read-only inspection has not completed its Verify boundary"
@@ -385,6 +388,12 @@ class InteractiveApplicationControlSession:
self._workspace_requested.set()
return self.snapshot()
def _require_workspace_entry_locked(self) -> None:
if (self._phase == "completed" and self._transport is not None
and not self._cancel_requested):
return
self._require_phase_locked("connection-ready")
def validate_connection_binding(self) -> None:
"""Fail closed when the DeviceInfo-bound route lost command authority."""
@@ -695,6 +704,7 @@ class InteractiveApplicationControlSession:
"connection-ready",
"workspace-ready",
"project-ready",
"completed",
}:
raise ApplicationAcceptanceError(
"control session can be closed safely only between pre-START checkpoints"
@@ -825,9 +835,13 @@ class InteractiveApplicationControlSession:
"state": phase,
"session_generation": self._run_generation,
"state_revision": self._state_revision,
"control_socket_open": phase not in {"idle", "completed", "closed", "failed"},
"control_socket_open": (
self._transport is not None and phase not in {"idle", "closed", "failed"}
),
"can_open": self._can_open_locked(),
"can_enter_workspace": phase == "connection-ready",
"can_enter_workspace": phase == "connection-ready" or (
phase == "completed" and self._transport is not None
),
"can_prepare_project": phase == "workspace-ready",
"can_start": phase == "project-ready",
"can_stop": phase == "scanning",
@@ -854,6 +868,7 @@ class InteractiveApplicationControlSession:
executor: PhysicalAcceptanceDialogueExecutor | None = None
transport: ReviewedApplicationMqttTransport | None = None
stop_publish_attempts_before_dispatch: int | None = None
completed_acquisition = False
try:
with self._lock:
host = self._host
@@ -862,265 +877,301 @@ class InteractiveApplicationControlSession:
raise ApplicationAcceptanceError("control session inputs are unavailable")
authority = self._authority_loader.load()
transport = self._transport_factory(host)
coordinator = self._physical_command_coordinator
if coordinator is not None:
transport.install_evidence_observer(coordinator)
if self._connection_path_validator is not None:
self._validate_connection_path("control-open-preflight")
transport.install_dispatch_guard(self._acquire_connection_dispatch_lease)
with self._lock:
self._transport = transport
transport.open()
orchestrator = ShadowApplicationBootstrapOrchestrator(
authority,
epoch_seconds=self._epoch_seconds(),
timezone_name=timezone_name,
)
executor = PhysicalAcceptanceDialogueExecutor(transport)
with self._lock:
inspection_only = self._inspection_only
# DeviceInfo (ordinal 1) is the only bootstrap request that may
# cross the socket before durable physical-target admission. In
# particular, ordinal 4 mutates the K1 clock, so the legacy
# collapsed ordinals 1-6 path must never run before the coordinator
# can reject a retired identity discovered under a fresh BLE
# transport UUID.
binding = executor.run_read_only_inspection_stage(orchestrator)
control_session_id = f"application-control-{generation}-{time.monotonic_ns()}"
with self._lock:
connection_binding = self._connection_binding
if coordinator is not None:
if connection_binding is None:
raise ApplicationAcceptanceError(
"durable physical control requires an exact connection binding"
)
coordinator.bind_control_session(
PhysicalCommandRuntimeBinding(
vendor_device_id_sha256=hash_physical_identity(binding.vendor_device_id),
device_serial_sha256=hash_physical_identity(binding.device_serial),
compatibility_profile_id=COMPATIBILITY_PROFILE_ID,
intent_id=connection_binding.intent_id,
transport_ref=connection_binding.transport_ref,
connection_mode=connection_binding.connection_mode,
target_ipv4=connection_binding.target_ipv4,
target_port=connection_binding.target_port,
host_path_epoch=connection_binding.host_path_epoch,
control_session_id=control_session_id,
producer_generation=generation,
)
while True:
transport = self._transport_factory(host)
if coordinator is not None:
transport.install_evidence_observer(coordinator)
if self._connection_path_validator is not None:
self._validate_connection_path("control-open-preflight")
transport.install_dispatch_guard(self._acquire_connection_dispatch_lease)
with self._lock:
self._transport = transport
transport.open()
completed_acquisition = False
orchestrator = ShadowApplicationBootstrapOrchestrator(
authority,
epoch_seconds=self._epoch_seconds(),
timezone_name=timezone_name,
)
if not inspection_only:
binding = executor.complete_connection_stage(
orchestrator,
expected_binding=binding,
)
# Publish the identity proof and the phase under one lock so a
# consumer cannot observe connection-ready without its DeviceInfo
# evidence (or evidence while still claiming to be connecting).
with self._lock:
self._control_authority = authority
self._live_control_binding = binding
transport_snapshot = self._live_transport_snapshot_locked()
self._verified_control = {
"logical_device_id": binding.vendor_device_id,
"compatibility_profile_id": COMPATIBILITY_PROFILE_ID,
"control_session_id": control_session_id,
"producer_generation": generation,
"source": "mqtt-device-info",
**self._control_proof_fields(transport_snapshot),
**(
{
"intent_id": connection_binding.intent_id,
"transport_ref": connection_binding.transport_ref,
"host_path_epoch": connection_binding.host_path_epoch,
"target_ipv4": connection_binding.target_ipv4,
"target_port": connection_binding.target_port,
"connection_mode": connection_binding.connection_mode,
}
if connection_binding is not None
else {}
),
}
self._set_phase_locked("connection-ready")
workspace = executor.wait_for_operator_checkpoint(
"workspace-entered",
self._workspace_requested.is_set,
reconciled_active_observed=self._active_recovery_requested.is_set,
)
if workspace is None:
self._validate_connection_binding("active-recovery-adoption")
executor.adopt_reconciled_scanning(
authority=authority,
binding=binding,
)
with self._scanning_transition_gate:
# Recovery adopts an already-active physical acquisition.
# Its checkpoint deliberately retains an open transport
# gap until the post-Rerun PCL confirmation hook closes it;
# nevertheless an operator STOP must remain admissible in
# this interval and can cease that gap with terminal READY.
self._scanning_observer_confirmed = True
self._set_phase("scanning")
else:
if inspection_only:
self._validate_connection_binding_snapshot(
"inspection-promotion-pre-dispatch"
executor = PhysicalAcceptanceDialogueExecutor(transport)
with self._lock:
inspection_only = self._inspection_only
# DeviceInfo (ordinal 1) is the only bootstrap request that may
# cross the socket before durable physical-target admission. In
# particular, ordinal 4 mutates the K1 clock, so the legacy
# collapsed ordinals 1-6 path must never run before the coordinator
# can reject a retired identity discovered under a fresh BLE
# transport UUID.
binding = executor.run_read_only_inspection_stage(orchestrator)
control_session_id = f"application-control-{generation}-{time.monotonic_ns()}"
with self._lock:
connection_binding = self._connection_binding
if coordinator is not None:
if connection_binding is None:
raise ApplicationAcceptanceError(
"durable physical control requires an exact connection binding"
)
coordinator.bind_control_session(
PhysicalCommandRuntimeBinding(
vendor_device_id_sha256=hash_physical_identity(binding.vendor_device_id),
device_serial_sha256=hash_physical_identity(binding.device_serial),
compatibility_profile_id=COMPATIBILITY_PROFILE_ID,
intent_id=connection_binding.intent_id,
transport_ref=connection_binding.transport_ref,
connection_mode=connection_binding.connection_mode,
target_ipv4=connection_binding.target_ipv4,
target_port=connection_binding.target_port,
host_path_epoch=connection_binding.host_path_epoch,
control_session_id=control_session_id,
producer_generation=generation,
)
)
if not inspection_only:
binding = executor.complete_connection_stage(
orchestrator,
expected_binding=binding,
)
self._validate_connection_binding_snapshot(
"inspection-promotion-post-response"
# Publish the identity proof and the phase under one lock so a
# consumer cannot observe connection-ready without its DeviceInfo
# evidence (or evidence while still claiming to be connecting).
with self._lock:
self._control_authority = authority
self._live_control_binding = binding
transport_snapshot = self._live_transport_snapshot_locked()
self._verified_control = {
"logical_device_id": binding.vendor_device_id,
"compatibility_profile_id": COMPATIBILITY_PROFILE_ID,
"control_session_id": control_session_id,
"producer_generation": generation,
"source": "mqtt-device-info",
**self._control_proof_fields(transport_snapshot),
**(
{
"intent_id": connection_binding.intent_id,
"transport_ref": connection_binding.transport_ref,
"host_path_epoch": connection_binding.host_path_epoch,
"target_ipv4": connection_binding.target_ipv4,
"target_port": connection_binding.target_port,
"connection_mode": connection_binding.connection_mode,
}
if connection_binding is not None
else {}
),
}
self._set_phase_locked(
"workspace-requested" if self._workspace_requested.is_set()
else "connection-ready"
)
self._validate_connection_binding_snapshot("workspace-entry-pre-dispatch")
executor.run_workspace_entry_stage(
orchestrator,
workspace,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"workspace-entry-dispatch"
),
)
self._validate_connection_binding_snapshot("workspace-entry-post-response")
self._set_phase("workspace-ready")
project = executor.wait_for_operator_checkpoint(
"project-prompt-opened",
self._project_requested.is_set,
workspace = executor.wait_for_operator_checkpoint(
"workspace-entered",
self._workspace_requested.is_set,
reconciled_active_observed=self._active_recovery_requested.is_set,
)
assert project is not None
self._validate_connection_binding_snapshot("project-prompt-pre-dispatch")
binding = executor.run_project_prompt_stage(
orchestrator,
project,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"project-prompt-dispatch"
),
)
self._validate_connection_binding_snapshot("project-prompt-post-response")
self._set_phase("project-ready")
start_checkpoint = executor.wait_for_operator_checkpoint(
"start-confirmed",
self._start_requested.is_set,
)
assert start_checkpoint is not None
start_command, start_confirmation = self._start_request()
start_permit = PhysicalAcceptancePermit(
start_confirmation.checklist(ModelingAction.START)
)
self._validate_connection_binding_snapshot("start-pre-dispatch")
self._set_phase("initializing")
start_active_observed = False
start_transition_gate_acquired = False
def observe_start_active() -> None:
nonlocal start_active_observed, start_transition_gate_acquired
if start_active_observed:
return
self._scanning_transition_gate.acquire()
start_transition_gate_acquired = True
try:
if coordinator is not None:
coordinator.resolve("start")
# Activate the durable recovery checkpoint at the same
# exact SCANNING proof as the physical ledger. The
# gate remains held while the read-only ordinals 13-14
# finish, so STOP cannot overtake the later public
# ``scanning`` transition.
if not self._scanning_observer_confirmed:
self._scanning_observer_confirmed = (
self._notify_scanning_observer()
)
start_active_observed = True
except BaseException:
self._scanning_transition_gate.release()
start_transition_gate_acquired = False
raise
try:
executor.execute_canonical_start(
start_command,
build_canonical_post_start_observation(authority, binding),
if workspace is None:
self._validate_connection_binding("active-recovery-adoption")
executor.adopt_reconciled_scanning(
authority=authority,
binding=binding,
permit=start_permit,
checkpoint=start_checkpoint,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"start-dispatch"
),
start_active_observer=observe_start_active,
)
# Compatibility executors used by lower-level integrations may
# return without invoking the new proof callback. A normal
# production executor invokes it before the read-only refresh;
# this idempotent fallback remains strictly post-success.
observe_start_active()
self._validate_connection_binding_snapshot("start-post-response")
self._set_phase("scanning")
finally:
if start_transition_gate_acquired:
self._scanning_transition_gate.release()
with self._scanning_transition_gate:
# Recovery adopts an already-active physical acquisition.
# Its checkpoint deliberately retains an open transport
# gap until the post-Rerun PCL confirmation hook closes it;
# nevertheless an operator STOP must remain admissible in
# this interval and can cease that gap with terminal READY.
self._scanning_observer_confirmed = True
self._set_phase("scanning")
else:
if inspection_only:
self._validate_connection_binding_snapshot(
"inspection-promotion-pre-dispatch"
)
binding = executor.complete_connection_stage(
orchestrator,
expected_binding=binding,
)
self._validate_connection_binding_snapshot(
"inspection-promotion-post-response"
)
self._validate_connection_binding_snapshot("workspace-entry-pre-dispatch")
executor.run_workspace_entry_stage(
orchestrator,
workspace,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"workspace-entry-dispatch"
),
)
self._validate_connection_binding_snapshot("workspace-entry-post-response")
self._set_phase("workspace-ready")
executor.maintain_active_until_stop_requested(self._stop_requested.is_set)
(
stop_command,
stop_confirmation,
stop_dispatch_admission_deadline_reached,
) = self._stop_request()
stop_permit = PhysicalAcceptancePermit(stop_confirmation.checklist(ModelingAction.STOP))
# Capture the exact transport counter before any remaining
# read-only validation. A deadline that expires during one of
# those checks is still deterministic zero-publish evidence.
stop_transport_before, stop_transport_before_available = (
self._transport_snapshot_safely(transport)
)
if stop_transport_before_available:
stop_publish_attempts_before_dispatch = self._json_int_or_none(
stop_transport_before.get("publish_attempts")
project = executor.wait_for_operator_checkpoint(
"project-prompt-opened",
self._project_requested.is_set,
)
assert project is not None
self._validate_connection_binding_snapshot("project-prompt-pre-dispatch")
binding = executor.run_project_prompt_stage(
orchestrator,
project,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"project-prompt-dispatch"
),
)
self._validate_connection_binding_snapshot("project-prompt-post-response")
self._set_phase("project-ready")
start_checkpoint = executor.wait_for_operator_checkpoint(
"start-confirmed",
self._start_requested.is_set,
)
assert start_checkpoint is not None
start_command, start_confirmation = self._start_request()
start_permit = PhysicalAcceptancePermit(
start_confirmation.checklist(ModelingAction.START)
)
self._validate_connection_binding_snapshot("start-pre-dispatch")
self._set_phase("initializing")
start_active_observed = False
start_transition_gate_acquired = False
def observe_start_active() -> None:
nonlocal start_active_observed, start_transition_gate_acquired
if start_active_observed:
return
self._scanning_transition_gate.acquire()
start_transition_gate_acquired = True
try:
if coordinator is not None:
coordinator.resolve("start")
# Activate the durable recovery checkpoint at the same
# exact SCANNING proof as the physical ledger. The
# gate remains held while the read-only ordinals 13-14
# finish, so STOP cannot overtake the later public
# ``scanning`` transition.
if not self._scanning_observer_confirmed:
self._scanning_observer_confirmed = (
self._notify_scanning_observer()
)
start_active_observed = True
except BaseException:
self._scanning_transition_gate.release()
start_transition_gate_acquired = False
raise
try:
executor.execute_canonical_start(
start_command,
build_canonical_post_start_observation(authority, binding),
authority=authority,
binding=binding,
permit=start_permit,
checkpoint=start_checkpoint,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"start-dispatch"
),
start_active_observer=observe_start_active,
)
# Compatibility executors used by lower-level integrations may
# return without invoking the new proof callback. A normal
# production executor invokes it before the read-only refresh;
# this idempotent fallback remains strictly post-success.
observe_start_active()
self._validate_connection_binding_snapshot("start-post-response")
self._set_phase("scanning")
finally:
if start_transition_gate_acquired:
self._scanning_transition_gate.release()
executor.maintain_active_until_stop_requested(self._stop_requested.is_set)
(
stop_command,
stop_confirmation,
stop_dispatch_admission_deadline_reached,
) = self._stop_request()
stop_permit = PhysicalAcceptancePermit(
stop_confirmation.checklist(ModelingAction.STOP)
)
self._require_stop_dispatch_deadline_open(
stop_dispatch_admission_deadline_reached
)
self._validate_connection_binding_snapshot("stop-pre-dispatch")
self._require_stop_dispatch_deadline_open(
stop_dispatch_admission_deadline_reached
)
self._set_phase("stopping")
logger.info(
"K1 STOP timing checkpoint",
extra={
"event_code": "k1_stop_dispatch_timing",
"operation_stage": "pre-dispatch-validation-complete",
"device_command_sent": False,
},
)
executor.execute_canonical_stop(
stop_command,
stop_permit,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"stop-dispatch"
),
dispatch_admission_deadline_reached=(
# Capture the exact transport counter before any remaining
# read-only validation. A deadline that expires during one of
# those checks is still deterministic zero-publish evidence.
stop_transport_before, stop_transport_before_available = (
self._transport_snapshot_safely(transport)
)
if stop_transport_before_available:
stop_publish_attempts_before_dispatch = self._json_int_or_none(
stop_transport_before.get("publish_attempts")
)
self._require_stop_dispatch_deadline_open(
stop_dispatch_admission_deadline_reached
),
)
logger.info(
"K1 STOP timing checkpoint",
extra={
"event_code": "k1_stop_dispatch_timing",
"operation_stage": "correlated-application-response",
"device_command_sent": True,
},
)
self._validate_connection_binding_snapshot("stop-post-response")
self._set_phase("awaiting-standby-confirmation")
executor.maintain_post_stop_until_standby()
if coordinator is not None:
coordinator.resolve("stop")
self._set_phase("completed")
)
self._validate_connection_binding_snapshot("stop-pre-dispatch")
self._require_stop_dispatch_deadline_open(
stop_dispatch_admission_deadline_reached
)
self._set_phase("stopping")
logger.info(
"K1 STOP timing checkpoint",
extra={
"event_code": "k1_stop_dispatch_timing",
"operation_stage": "pre-dispatch-validation-complete",
"device_command_sent": False,
},
)
executor.execute_canonical_stop(
stop_command,
stop_permit,
dispatch_guard=lambda: self._validate_connection_binding_snapshot(
"stop-dispatch"
),
dispatch_admission_deadline_reached=(
stop_dispatch_admission_deadline_reached
),
)
logger.info(
"K1 STOP timing checkpoint",
extra={
"event_code": "k1_stop_dispatch_timing",
"operation_stage": "correlated-application-response",
"device_command_sent": True,
},
)
self._validate_connection_binding_snapshot("stop-post-response")
self._set_phase("awaiting-standby-confirmation")
executor.maintain_post_stop_until_standby()
if coordinator is not None:
coordinator.resolve("stop")
with self._lock:
# Clear only the acquisition checkpoints before publishing READY.
# Network ownership and DeviceInfo proof remain on this socket.
self._workspace_requested.clear()
self._active_recovery_requested.clear()
self._project_requested.clear()
self._start_requested.clear()
self._stop_requested.clear()
self._start_confirmation = None
self._stop_confirmation = None
self._stop_dispatch_admission_deadline_reached = None
self._prepared_start_command = None
self._prepared_stop_command = None
self._project_name = None
self._scanning_observer_confirmed = self._scanning_observer is None
completed_acquisition = True
self._set_phase_locked("completed")
executor.maintain_standby_until_next_acquisition(self._workspace_requested.is_set)
self._validate_connection_binding_snapshot("next-acquisition-preflight")
# Operation keys and response correlations are one-shot per MQTT
# dialogue. A new explicit scan gets a fresh socket, never reset
# consumption sets or ambiguous reuse of the old response IDs.
transport.close()
with self._lock:
self._transport = None
self._verified_control = None
self._run_generation += 1
generation = self._run_generation
executor = None
stop_publish_attempts_before_dispatch = None
except Exception as exc:
dialogue_snapshot, dialogue_snapshot_available = self._executor_snapshot_safely(
executor
@@ -1217,7 +1268,9 @@ class InteractiveApplicationControlSession:
and stop_command_attempted is False
and not diagnostic_evidence_unavailable
)
outcome_unknown = not definite_stop_admission_rejected_before_publish and (
outcome_unknown = not (
definite_stop_admission_rejected_before_publish or completed_acquisition
) and (
isinstance(exc, ApplicationCommandOutcomeUnknown)
or modeling_command_attempted is True
or bool(diagnostic_evidence_unavailable)
@@ -1288,7 +1341,7 @@ class InteractiveApplicationControlSession:
)
is None
)
safe_to_retry = status_reconciled_prestart_failure or (
safe_to_retry = completed_acquisition or status_reconciled_prestart_failure or (
not outcome_unknown
and (
not transport_created