fix(k1): fence start endpoint handoff

This commit is contained in:
DCCONSTRUCTIONS
2026-08-14 17:40:07 +03:00
parent 9e6b5b403a
commit 3235ba7b1c
3 changed files with 522 additions and 14 deletions
+227
View File
@@ -90,6 +90,7 @@ from k1link.device_plugins.xgrids_k1.network_provisioning_idempotency_journal im
)
from k1link.device_plugins.xgrids_k1.physical_command_coordinator import (
LedgerPhysicalCommandCoordinator,
PhysicalCommandIntentContext,
PhysicalCommandRuntimeBinding,
)
from k1link.device_plugins.xgrids_k1.physical_command_ledger import (
@@ -7502,6 +7503,151 @@ def test_plugin_commanded_acquisition_keeps_start_and_stop_as_explicit_actions(
)
def test_terminal_prepared_start_without_publish_settles_and_stops_local_capture(
tmp_path: Path,
) -> None:
service, runtime = service_with_fake_runtime(tmp_path)
control = FakeInteractiveControlSession()
service._application_control_session = control # type: ignore[assignment] # noqa: SLF001
binding = _seed_supervised_connection(
service,
transport_ref="k1-start-zero-publish",
)
prepared = service.prepare_acquisition(
_prepare_request(
project_name="START_NONE",
host=binding.target_ipv4,
compatibility_attestation=ATTESTATION,
)
)
acquisition_id = str(prepared["acquisition"]["acquisition_id"])
identity = PhysicalCommandIdentity(
vendor_device_id_sha256="a" * 64,
device_serial_sha256="b" * 64,
)
assert control.verified_control is not None
connection = PhysicalCommandConnectionBinding(
intent_id=binding.intent_id,
transport_ref=binding.transport_ref,
connection_mode=binding.connection_mode,
target_ipv4=binding.target_ipv4,
target_port=binding.target_port,
host_path_epoch=binding.host_path_epoch,
control_session_id=str(control.verified_control["control_session_id"]),
producer_generation=int(control.verified_control["producer_generation"]),
)
coordinator = service._physical_command_coordinator # noqa: SLF001
coordinator.application_response(
ApplicationMqttResponseEvidence(
operation_key="bootstrap:start-none:DeviceInfoRequest",
response_topic="lixel/application/response/device_info",
payload_sha256="c" * 64,
modeling_action=None,
result_code=None,
success=None,
observed_at_utc="2026-08-14T13:30:40.000Z",
)
)
coordinator.bind_control_session(
PhysicalCommandRuntimeBinding(
vendor_device_id_sha256=identity.vendor_device_id_sha256,
device_serial_sha256=identity.device_serial_sha256,
compatibility_profile_id=XGRIDS_K1_COMPATIBILITY_PROFILE_ID,
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,
)
)
coordinator.device_status(
ApplicationMqttDeviceStatusEvidence(
vendor_device_id_sha256=identity.vendor_device_id_sha256,
device_serial_sha256=identity.device_serial_sha256,
session_state="ready",
session_state_code=MODELING_STATE_BASE + 300,
project_bound=False,
project_id_sha256=None,
init_ready=False,
status_message_sha256="d" * 64,
mqtt_retained=False,
observed_at_utc="2026-08-14T13:30:41.000Z",
)
)
payload = b"exact-start-zero-publish"
envelope = OneShotPublishEnvelope(
operation_key="modeling:start",
topic="lixel/application/request/modeling",
payload=payload,
payload_sha256=hashlib.sha256(payload).hexdigest(),
payload_bytes=len(payload),
qos=2,
retain=False,
)
def request_start_after_durable_prepare(**kwargs: object) -> dict[str, object]:
context = kwargs["command_context"]
observer = kwargs["preparation_checkpoint_observer"]
assert isinstance(context, PhysicalCommandIntentContext)
assert callable(observer)
coordinator.prepare(context, action="start", envelope=envelope)
observer("prepared", context, envelope)
control._accept_checkpoint( # noqa: SLF001
expected_session_generation=kwargs["expected_session_generation"], # type: ignore[arg-type]
expected_state_revision=kwargs["expected_state_revision"], # type: ignore[arg-type]
)
control.start_projects.append(str(kwargs["project_name"]))
control.start_contexts.append(context)
control.state = "start-requested"
return control.snapshot()
control.request_start = request_start_after_durable_prepare # type: ignore[method-assign]
starting = service.start_acquisition(
_start_request(
acquisition_id=acquisition_id,
physical_acceptance=PHYSICAL_ACCEPTANCE,
)
)
start_operation_id = str(starting["last_operation"]["operation_id"])
assert starting["physical_command"]["record"]["stage"] == "prepared"
assert starting["active_acquisition_recovery_checkpoint"]["state"] == "prepared"
assert runtime.source_mode == "live"
control.state = "failed"
control.state_revision += 1
control.failure = {
"reason_code": "application-connection-binding-lost",
"failed_phase": "start-requested",
"modeling_command_attempted": False,
"diagnostic_evidence_unavailable": [],
"safe_to_retry": True,
}
control.outcome_unknown = False
settled = service.state()
record = service._physical_command_ledger.snapshot().record # noqa: SLF001
checkpoint = service._active_acquisition_checkpoint.snapshot().checkpoint # type: ignore[union-attr] # noqa: SLF001
operation = service._operations.get(start_operation_id) # noqa: SLF001
assert record is not None
assert record.stage == "resolved"
assert record.resolution == "not-dispatched"
assert checkpoint is not None and checkpoint.state == "ceased"
assert settled["acquisition"]["state"] == "failed"
assert settled["acquisition"]["result"]["device_start"] == "not-dispatched"
assert settled["acquisition"]["result"]["receiver_stopped"] is True
assert operation.status == "failed"
assert operation.stage_code == "physical-start-not-dispatched"
assert operation.error is not None
assert operation.error["side_effect_status"] == "none"
assert operation.error["physical_command_sent"] is False
assert runtime.stop_calls == 1
assert runtime.source_mode == "idle"
assert service._acquisition_session_lease is None # noqa: SLF001
def _activate_real_checkpoint_for_prepared_stop_fixture(
service: XgridsK1CompatibilityService,
*,
@@ -12879,6 +13025,11 @@ def test_public_commanded_workflow_refreshes_stable_route_before_ttl_reduction(
control, binding, ready = _install_binding_validating_ready_control(service)
stable_path = _direct_host_path(binding.target_ipv4)
monkeypatch.setattr(service, "_sample_host_path", lambda *_args, **_kwargs: stable_path)
monkeypatch.setattr(
facade_module,
"_probe_control_endpoint_socket",
lambda _target: facade_module.TcpReachabilityProbeResult(reachable=True),
)
target = EndpointTarget(binding.target_ipv4, binding.target_port)
def advance_past_fifteen_seconds_with_fresh_endpoint() -> None:
@@ -13244,6 +13395,82 @@ def test_start_refreshes_ttl_expired_endpoint_after_project_prepare(
assert control.start_projects == [PROJECT_NAME]
def test_start_refreshes_endpoint_before_prepared_worker_handoff_window(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
service, runtime = service_with_fake_runtime(tmp_path)
monotonic_now = [100.0]
suspend_aware_now = [1_000.0]
supervisor = service._connection_supervisor # noqa: SLF001
supervisor._monotonic_clock = lambda: monotonic_now[0] # noqa: SLF001
supervisor._suspend_aware_clock = lambda: suspend_aware_now[0] # noqa: SLF001
supervisor._observation_ttl_seconds = 15.0 # noqa: SLF001
control, binding, ready = _install_binding_validating_ready_control(service)
stable_path = _direct_host_path(binding.target_ipv4)
monkeypatch.setattr(service, "_sample_host_path", lambda *_args, **_kwargs: stable_path)
workspace = service.enter_application_workspace(
EnterApplicationWorkspaceRequest(
operator_confirmed=True,
expected_session_generation=ready["application_control_session"][
"session_generation"
],
expected_state_revision=ready["application_control_session"]["state_revision"],
)
)
prepared = service.prepare_acquisition(
_prepare_request(
project_name=PROJECT_NAME,
host=binding.target_ipv4,
compatibility_attestation=ATTESTATION,
expected_control_session_generation=workspace["application_control_session"][
"session_generation"
],
expected_control_state_revision=workspace["application_control_session"][
"state_revision"
],
)
)
# The endpoint is still nominally reachable, but only eleven seconds of
# its lease remain. That is insufficient for PREPARED + worker dispatch.
monotonic_now[0] += 4.01
suspend_aware_now[0] += 4.01
assert supervisor.snapshot().endpoint.tcp_state == "reachable"
tcp_samples: list[str] = []
monkeypatch.setattr(
facade_module,
"_probe_control_endpoint_socket",
lambda target: (
tcp_samples.append(target)
or facade_module.TcpReachabilityProbeResult(reachable=True)
),
)
started = service.start_acquisition(
_start_request(
acquisition_id=prepared["acquisition"]["acquisition_id"],
physical_acceptance=PHYSICAL_ACCEPTANCE,
expected_control_session_generation=prepared["application_control_session"][
"session_generation"
],
expected_control_state_revision=prepared["application_control_session"][
"state_revision"
],
)
)
assert started["acquisition"]["state"] == "starting"
assert tcp_samples == [binding.target_ipv4]
assert supervisor.endpoint_observation_has_remaining_lease(
target=EndpointTarget(binding.target_ipv4, binding.target_port),
intent_id=binding.intent_id,
host_path_epoch=binding.host_path_epoch,
minimum_remaining_seconds=facade_module.COMMAND_ENDPOINT_MIN_REMAINING_SECONDS,
)
assert len(runtime.start_calls) == 1
assert control.start_projects == [PROJECT_NAME]
def test_start_stale_control_proof_fails_before_receiver_or_device_checkpoint(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,