feat(host-agent): stop assignment execution collaboratively on cancellation

- LeaseGuard gains an is_cancellation convenience property
- ActiveAssignmentRunner marks the lease lost with a cancellation
  reason when a renewal response reports cancel_requested
- AssignmentExecutor threads stop_reason through to TaskRunner/
  WorkflowRunner and maps a cancellation-flavored stop to
  AssignmentExecutionResult.status = "cancelled" instead of "failed"
- AssignmentProcessor forwards a three-way done/cancelled/failed
  status when reporting the terminal result
- Add/extend tests across lease, assignment, processor, and client
This commit is contained in:
2026-07-15 18:28:21 +08:00
parent 8a0d48eada
commit d3024b4810
9 changed files with 233 additions and 22 deletions
@@ -140,6 +140,30 @@ def test_stale_lease_response_raises_typed_error_without_retry() -> None:
assert attempts == 1
def test_renew_deserializes_cancel_requested_flag() -> None:
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(
200,
json={
"status": "renewed",
"lease_expires_at": "2026-07-12T00:05:00Z",
"cancel_requested": True,
},
)
async def scenario() -> None:
async with httpx.AsyncClient(
transport=httpx.MockTransport(handler),
base_url="https://control.example",
) as http_client:
client = HostAgentClient(_config(), http_client=http_client)
response = await client.renew(_assignment())
assert response.status == "renewed"
assert response.cancel_requested is True
asyncio.run(scenario())
def test_result_report_retries_identical_payload_after_response_loss() -> None:
payloads: list[dict[str, object]] = []