fix(k1): allow explicit reset after terminal start fault
This commit is contained in:
@@ -8477,6 +8477,7 @@ class XgridsK1CompatibilityService:
|
||||
acquisition_id=(
|
||||
acquisition.acquisition_id if acquisition is not None else None
|
||||
),
|
||||
allow_terminal_failure_retirement=True,
|
||||
)
|
||||
)
|
||||
self._supersede_control_bootstrap_continuation()
|
||||
@@ -27415,6 +27416,7 @@ class XgridsK1CompatibilityService:
|
||||
self,
|
||||
*,
|
||||
acquisition_id: str | None,
|
||||
allow_terminal_failure_retirement: bool = False,
|
||||
) -> tuple[_LocalStopRetirementDisposition, str | None]:
|
||||
"""Cancel the local worker under the exact publish fence.
|
||||
|
||||
@@ -27450,6 +27452,24 @@ class XgridsK1CompatibilityService:
|
||||
)
|
||||
self._application_control_session.close()
|
||||
retired_control = dict(self._application_control_session.snapshot())
|
||||
if (
|
||||
allow_terminal_failure_retirement
|
||||
and retired_control.get("state") == "failed"
|
||||
):
|
||||
# ``can_open`` deliberately remains false after an ambiguous
|
||||
# START/STOP failure even when the socket worker has already
|
||||
# exited. That is the correct admission policy for an
|
||||
# ordinary retry, but an explicit scenario reset is a
|
||||
# different operation: retire only the dead local owner while
|
||||
# the durable physical ledger below preserves the ambiguous
|
||||
# device outcome. ``retire_for_network_change`` still checks
|
||||
# that the thread and transport are actually gone, so a live
|
||||
# publisher cannot be detached by this path.
|
||||
with suppress(AttributeError, RuntimeError):
|
||||
self._retire_application_control_for_network_change(
|
||||
allow_terminal_failure=True,
|
||||
)
|
||||
retired_control = dict(self._application_control_session.snapshot())
|
||||
if (
|
||||
retired_control.get("state")
|
||||
not in {"idle", "completed", "closed", "failed"}
|
||||
|
||||
@@ -1722,6 +1722,72 @@ def test_terminal_cleanup_pending_is_locally_sealed_by_mode_reset(
|
||||
assert reset["connection_scenario_reset"]["network_write_performed"] is False
|
||||
|
||||
|
||||
def test_terminal_ambiguous_start_failure_can_be_locally_reset_after_worker_exit(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = _service(monkeypatch, tmp_path)
|
||||
_seed_resettable_physical_record(service)
|
||||
acquisition = AcquisitionRecord(
|
||||
acquisition_id="reset-preflight-acquisition-0001",
|
||||
device_id="device-reset-terminal-start-fault",
|
||||
device_session_id="device-session-reset-terminal-start-fault",
|
||||
compatibility_profile_id=("xgrids.lixelkity-k1.fw-3.0.2.local-network.v2"),
|
||||
control_mode="plugin-commanded",
|
||||
requested_streams=("points",),
|
||||
target_host="192.168.68.51",
|
||||
duration_seconds=None,
|
||||
evidence_policy="required",
|
||||
)
|
||||
acquisition.transition(
|
||||
"failed",
|
||||
message_code="acquisition.start.device_outcome_unknown",
|
||||
result={
|
||||
"receiver_stopped": False,
|
||||
"device_start": "outcome-unknown",
|
||||
},
|
||||
)
|
||||
control = service._application_control_session # noqa: SLF001
|
||||
with control._lock: # noqa: SLF001
|
||||
control._set_phase_locked("failed") # noqa: SLF001
|
||||
control._failure = { # noqa: SLF001
|
||||
"reason_code": "device_status_fault",
|
||||
"safe_to_retry": False,
|
||||
"modeling_command_attempted": True,
|
||||
"outcome_unknown": True,
|
||||
}
|
||||
control._thread = None # noqa: SLF001
|
||||
control._transport = None # noqa: SLF001
|
||||
with service._lock: # noqa: SLF001
|
||||
service._acquisition = acquisition # noqa: SLF001
|
||||
|
||||
before = control.snapshot()
|
||||
assert before["state"] == "failed"
|
||||
assert before["can_open"] is False
|
||||
|
||||
reset = service.select_connection_mode(
|
||||
_reset(
|
||||
mode="bridge",
|
||||
revision=0,
|
||||
reset_id="op-reset-terminal-ambiguous-start-01",
|
||||
)
|
||||
)
|
||||
|
||||
assert reset["desired_connection_mode_revision"] == 1
|
||||
assert reset["connection_scenario_reset"]["local_session_closed"] is True
|
||||
assert reset["connection_scenario_reset"]["physical_disposition"] == (
|
||||
"operator-retired-outcome-unknown"
|
||||
)
|
||||
assert reset["connection_scenario_reset"]["device_command_performed"] is False
|
||||
assert reset["connection_scenario_reset"]["network_write_performed"] is False
|
||||
assert reset["application_control_session"]["state"] == "idle"
|
||||
assert reset["acquisition"]["state"] == "failed"
|
||||
assert reset["acquisition"]["cleanup_pending"] is False
|
||||
physical = service._physical_command_ledger.snapshot().record # noqa: SLF001
|
||||
assert physical is not None
|
||||
assert physical.resolution == "operator-retired-outcome-unknown"
|
||||
|
||||
|
||||
def test_terminal_cleanup_failure_stays_visible_and_exact_reset_can_retry(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
|
||||
Reference in New Issue
Block a user