feat(cloud): add durable cancellation support to task repository
- Add nullable cancel_requested_at column (migration 0012) - Widen ScheduledTaskStatus/TerminalTaskStatus to include cancelled - Add CancellationRequestStatus + request_task_cancellation() to CloudRepository protocol and SQLAlchemy implementation - renew_lease() now returns LeaseRenewalResult, surfacing whether cancellation is pending, instead of a bare status string - reap_expired_leases() resolves pending-cancellation tasks to cancelled instead of requeuing/failing them - record_task_result() accepts cancelled and clears cancel_requested_at on any terminal write Note: internal_api/api.py's renew_assignment route still compares renew_lease()'s return value against a bare string; it will be updated in the next task (Internal Host<->Cloud protocol) to consume LeaseRenewalResult and populate the new cancel_requested wire field.
This commit is contained in:
@@ -30,9 +30,12 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
AttemptStatus = Literal["assigned", "dispatched", "done", "failed", "expired"]
|
||||
TerminalTaskStatus = Literal["done", "failed"]
|
||||
TerminalTaskStatus = Literal["done", "failed", "cancelled"]
|
||||
ResultRecordStatus = Literal["recorded", "already_recorded", "conflict"]
|
||||
LeaseRenewalStatus = Literal["renewed", "not_found", "conflict", "expired"]
|
||||
CancellationRequestStatus = Literal[
|
||||
"requested", "already_terminal", "already_requested", "not_found"
|
||||
]
|
||||
|
||||
|
||||
class HostEnrollmentConflictError(RuntimeError):
|
||||
@@ -91,6 +94,19 @@ class TaskAttemptRecord:
|
||||
terminal_result: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LeaseRenewalResult:
|
||||
"""Outcome of a lease renewal, including whether cancellation is pending.
|
||||
|
||||
``cancel_requested`` reflects the task's durable ``cancel_requested_at``
|
||||
column at renewal time regardless of ``status`` — callers only act on it
|
||||
when ``status == "renewed"``.
|
||||
"""
|
||||
|
||||
status: LeaseRenewalStatus
|
||||
cancel_requested: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LeasedAssignment:
|
||||
task_id: str
|
||||
@@ -485,7 +501,7 @@ class CloudRepository(Protocol):
|
||||
lease_expires_at: datetime,
|
||||
now: datetime,
|
||||
progress: AssignmentProgressSnapshot | None = None,
|
||||
) -> LeaseRenewalStatus: ...
|
||||
) -> LeaseRenewalResult: ...
|
||||
|
||||
def record_task_result(
|
||||
self,
|
||||
@@ -500,6 +516,13 @@ class CloudRepository(Protocol):
|
||||
completed_at: datetime,
|
||||
) -> ResultRecordStatus: ...
|
||||
|
||||
def request_task_cancellation(
|
||||
self,
|
||||
task_id: str,
|
||||
*,
|
||||
requested_at: datetime,
|
||||
) -> CancellationRequestStatus: ...
|
||||
|
||||
def reap_expired_leases(
|
||||
self,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user