docs(openspec): add task-cancellation proposal, design, specs, tasks

This commit is contained in:
2026-07-15 17:38:02 +08:00
parent a25542694d
commit 947434b65a
9 changed files with 655 additions and 0 deletions
@@ -0,0 +1,24 @@
## MODIFIED Requirements
### Requirement: Task dashboard
The console SHALL render a task view listing tasks by status with pagination, and SHALL show a task's detail including its attempt history, using the platform SDK's task-listing and attempt-history endpoints. The task detail view SHALL offer a Cancel action for tasks in status `queued`, `assigned`, or `dispatched`, using the platform SDK's cancel endpoint, and the status filter SHALL include `cancelled`.
#### Scenario: Browse the task queue
- **WHEN** an operator with a `tasks:read`-scoped token opens the task view
- **THEN** the console displays tasks with their status, goal or workflow reference, and assigned device/host, and lets the operator filter by status, including `cancelled`
#### Scenario: Inspect a task's attempt history
- **WHEN** an operator selects a task from the list
- **THEN** the console displays that task's recorded attempts in order, including each attempt's outcome
#### Scenario: Cancel a task from the detail view
- **WHEN** an operator with a `tasks:submit`-scoped token views the detail of a task whose status is `queued`, `assigned`, or `dispatched`, and clicks Cancel
- **THEN** the console calls the cancel endpoint and updates the displayed status to reflect the immediate or pending cancellation result
#### Scenario: Cancel action is absent for terminal tasks
- **WHEN** an operator views the detail of a task whose status is `done`, `failed`, or `cancelled`
- **THEN** the console does not offer a Cancel action for that task
#### Scenario: Cancel attempted without submit scope
- **WHEN** an operator whose token lacks `tasks:submit` views a cancellable task's detail
- **THEN** the console does not offer a Cancel action, or surfaces the API's authorization error without implying the task was cancelled
@@ -0,0 +1,48 @@
## MODIFIED Requirements
### Requirement: Active execution renews its lease
The Host Agent SHALL renew the active assignment lease before expiry while execution continues, and SHALL treat loss or rejection of the lease, or an observed cancellation request, as a stop condition for further planned actions where interruption is possible.
#### Scenario: Lease renewal succeeds
- **WHEN** the owning Host Agent renews an unexpired active lease
- **THEN** the control plane extends its expiry without changing the task attempt or device assignment
#### Scenario: Lease is stale or foreign
- **WHEN** a Host Agent attempts to renew an expired, replaced, or differently owned lease
- **THEN** the control plane returns a conflict and does not revive or alter the current attempt
#### Scenario: Renewal response signals a pending cancellation
- **WHEN** the control plane's renewal response for an active lease indicates a pending cancellation request
- **THEN** the Host Agent stops further planned actions at the next available step boundary, the same way it stops on lease loss
### Requirement: Terminal result reporting is idempotent
The Host Agent SHALL report a terminal result using the task, attempt, and lease identifiers, and repeating the same report SHALL return the already recorded outcome without duplicating state transitions. The terminal result SHALL be `done`, `failed`, or `cancelled`.
#### Scenario: Report a successful result
- **WHEN** the active lease owner reports successful completion
- **THEN** the control plane marks the scheduled task done, releases the device reservation, and records the result metadata
#### Scenario: Report a cancelled result
- **WHEN** the active lease owner reports that its execution stopped because of an observed cancellation request
- **THEN** the control plane marks the scheduled task cancelled, releases the device reservation, and records the result metadata
#### Scenario: Retry a result after response loss
- **WHEN** the Host Agent repeats the identical terminal report for an already completed active lease
- **THEN** the control plane returns the recorded terminal result without creating a new attempt or error
#### Scenario: Stale attempt reports after requeue
- **WHEN** an expired earlier attempt reports after a newer attempt has been created
- **THEN** the control plane rejects the stale report and preserves the newer attempt's state
## ADDED Requirements
### Requirement: Host distinguishes cancellation stop from lease-loss stop when reporting outcome
The Host Agent SHALL track whether its active execution stopped because of an observed cancellation request or for another stop reason (lost or rejected lease), and SHALL report `cancelled` only in the cancellation case, reporting `failed` for other stop reasons.
#### Scenario: Stop triggered by cancellation
- **WHEN** the Host Agent's collaborative-stop mechanism is triggered by a renewal response signaling a pending cancellation
- **THEN** the terminal result it reports for that attempt is `cancelled`
#### Scenario: Stop triggered by lease loss
- **WHEN** the Host Agent's collaborative-stop mechanism is triggered by a rejected or lost lease unrelated to any cancellation signal
- **THEN** the terminal result it reports for that attempt is `failed`, not `cancelled`
@@ -0,0 +1,54 @@
## MODIFIED Requirements
### Requirement: Task submission and status via the SDK
The system SHALL allow an external integrator to submit a task (goal or workflow reference plus constraints) through the platform SDK's API, to query that task's current status by id, and to request cancellation of that task by id, backed by the `task-scheduler` capability.
#### Scenario: Submit a task via the API
- **WHEN** an integrator calls the task-submission endpoint with a valid goal and optional constraints
- **THEN** the API returns a task id that can be used to poll status, and the underlying `task-scheduler` records a new `queued` `ScheduledTask`
#### Scenario: Query status of a known task
- **WHEN** an integrator requests status for a task id that exists
- **THEN** the API returns that task's current status (`queued`, `assigned`, `dispatched`, `done`, `failed`, or `cancelled`)
#### Scenario: Query status of an unknown task
- **WHEN** an integrator requests status for a task id that does not exist
- **THEN** the API returns a not-found response rather than an unhandled server error
#### Scenario: Cancel a known task via the API
- **WHEN** an integrator with the required scope calls the cancel endpoint for a task id that exists and is not already `done` or `failed`
- **THEN** the API accepts the request and the underlying `task-scheduler` records the cancellation per its immediate or collaborative rules for that task's current status
#### Scenario: Cancel an unknown task
- **WHEN** an integrator calls the cancel endpoint for a task id that does not exist
- **THEN** the API returns a not-found response rather than an unhandled server error
### Requirement: Public API operations enforce scopes
The public platform API SHALL require operation-specific scopes, including task submission, task cancellation, task reading, pool reading, plugin reading, and plugin administration.
#### Scenario: Submit token has task scope
- **WHEN** a principal with `tasks:submit` calls the task-submission endpoint
- **THEN** the request is authorized subject to normal task validation
#### Scenario: Submit-scoped token cancels a task
- **WHEN** a principal with `tasks:submit` calls the task-cancellation endpoint for any task id
- **THEN** the request is authorized; the platform SDK does not restrict cancellation to the task's original submitter, since no per-task submitter identity is tracked
#### Scenario: Read-only token attempts cancellation
- **WHEN** a principal that holds only `tasks:read` calls the task-cancellation endpoint
- **THEN** the API rejects the request before contacting the scheduler
#### Scenario: Non-admin token attempts plugin registration
- **WHEN** an authenticated principal without `plugins:admin` calls plugin registration
- **THEN** the API rejects the request before resolving or loading the plugin target
### Requirement: Python SDK client mirrors the REST API
The system SHALL provide a Python client (`CloudClient`) exposing methods corresponding to each `/v1/...` route (submit task, get task status, cancel task, list devices, list hosts, list plugins, register plugin), so integrators do not need to hand-construct HTTP requests.
#### Scenario: Client submits a task and retrieves status
- **WHEN** a caller uses `CloudClient` to submit a task and then fetch its status by the returned id
- **THEN** the client's methods produce the same result as calling the corresponding `/v1/...` endpoints directly over HTTP
#### Scenario: Client cancels a task
- **WHEN** a caller uses `CloudClient` to cancel a task by id
- **THEN** the client's method produces the same result as calling the cancel endpoint directly over HTTP
@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: Queued task cancellation is immediate
The system SHALL, when a cancellation is requested against a task in status `queued`, transition that task directly to status `cancelled` synchronously within the same request, without contacting any Host.
#### Scenario: Cancel a task that has not been assigned
- **WHEN** an authorized caller requests cancellation of a task whose status is `queued`
- **THEN** the task's status becomes `cancelled` in the same request and no assignment or lease is ever created for it
### Requirement: In-flight task cancellation is a durable, collaborative request
The system SHALL, when a cancellation is requested against a task in status `assigned` or `dispatched`, durably record a cancellation request against that task rather than immediately marking it `cancelled`, and SHALL surface that pending request to the owning Host Agent no later than its next lease renewal.
#### Scenario: Cancel a task currently executing on a Host
- **WHEN** an authorized caller requests cancellation of a task whose status is `dispatched`
- **THEN** the system records the cancellation request against the task's current attempt, the task's status remains `dispatched` until the Host reports a terminal result, and the request survives a control-plane restart
#### Scenario: Owning Host observes the pending cancellation at lease renewal
- **WHEN** the Host Agent executing the task renews its lease after a cancellation request was recorded
- **THEN** the renewal response signals the pending cancellation and the Host Agent stops further planned actions at the next available step boundary
#### Scenario: Cancellation is not instantaneous
- **WHEN** a cancellation is requested against a `dispatched` task
- **THEN** the system does not guarantee the task reaches status `cancelled` before the owning Host's next lease-renewal cycle completes
### Requirement: Host reports a cancelled outcome distinct from a failed outcome
The Host Agent SHALL report a terminal status of `cancelled`, distinct from `failed`, when its active execution stopped because of an observed cancellation request rather than a lease loss or an execution error, and the control plane SHALL record that task as status `cancelled`.
#### Scenario: Execution stops due to a cancellation request
- **WHEN** the Host Agent's active `TaskRunner` or `WorkflowRunner` execution stops because a lease renewal signaled a pending cancellation
- **THEN** the Host Agent reports terminal status `cancelled`, and the control plane transitions the task to status `cancelled` and releases its device reservation
#### Scenario: Execution stops due to lease loss unrelated to cancellation
- **WHEN** the Host Agent's active execution stops because its lease was rejected or lost for a reason other than a pending cancellation
- **THEN** the Host Agent reports terminal status `failed`, not `cancelled`
### Requirement: Cancellation requests are idempotent
The system SHALL treat a repeated cancellation request against a task that already has a pending or completed cancellation as a no-op that returns the task's current status, rather than as an error.
#### Scenario: Cancel a task twice
- **WHEN** an authorized caller requests cancellation of a task that already has a pending cancellation request recorded
- **THEN** the system returns the same successful response as the first request without creating a duplicate cancellation record
#### Scenario: Cancel an already-cancelled task
- **WHEN** an authorized caller requests cancellation of a task whose status is already `cancelled`
- **THEN** the system returns success reflecting the `cancelled` status without error
### Requirement: Cancellation is rejected for tasks already in a terminal, non-cancelled state
The system SHALL reject a cancellation request against a task whose status is already `done` or `failed` with a clear conflict error, without altering that task's recorded outcome.
#### Scenario: Cancel a completed task
- **WHEN** an authorized caller requests cancellation of a task whose status is `done`
- **THEN** the system rejects the request with a conflict error and the task's status and result remain unchanged
### Requirement: An expiring lease on a task with a pending cancellation resolves to cancelled, not requeued
The system SHALL, when an active lease expires on a task that has a pending cancellation request, mark that task `cancelled` rather than returning it to `queued` for a further attempt.
#### Scenario: Lease expires while a cancellation is pending
- **WHEN** the active lease on a `dispatched` task with a pending cancellation request expires before a terminal result is reported
- **THEN** the task transitions to status `cancelled` and its device reservation is released, instead of being requeued for another attempt
@@ -0,0 +1,54 @@
## MODIFIED Requirements
### Requirement: Terminal transitions validate the active lease
The system SHALL accept a `done`, `failed`, or `cancelled` result only from the current active task attempt and lease and SHALL make repeated identical terminal reports idempotent.
#### Scenario: Active lease reports completion
- **WHEN** the active lease owner reports a terminal result
- **THEN** the task transitions once to done, failed, or cancelled and releases its device reservation
#### Scenario: Superseded lease reports completion
- **WHEN** a result references a lease superseded by expiry and retry
- **THEN** the result is rejected and cannot overwrite the current task attempt
### Requirement: Expired attempts follow bounded retry policy
The system SHALL detect expired assigned or dispatched leases and SHALL either requeue the task with its reservation released, mark it failed when the configured attempt limit is reached, or mark it cancelled when a cancellation request is pending against it.
#### Scenario: Lease expires with attempts remaining
- **WHEN** an active lease expires before a terminal result and the task has remaining attempts and no pending cancellation request
- **THEN** the task returns to queued, the previous device reservation is released, and the expired attempt remains auditable
#### Scenario: Lease expires at attempt limit
- **WHEN** an active lease expires and the task has reached its maximum attempts
- **THEN** the task becomes failed with a lease-expiry reason and its device reservation is released
#### Scenario: Lease expires with a cancellation pending
- **WHEN** an active lease expires on a task that has a pending cancellation request, regardless of remaining attempts
- **THEN** the task becomes cancelled rather than being requeued or marked failed, and its device reservation is released
## ADDED Requirements
### Requirement: Task status includes a reachable cancelled value
The `ScheduledTaskStatus` SHALL include `cancelled` as a terminal status reachable from `queued`, `assigned`, or `dispatched`, alongside the existing `done` and `failed` terminal statuses.
#### Scenario: Cancelled status is a valid terminal state
- **WHEN** a task's cancellation completes, whether immediately from `queued` or after collaborative stop from `assigned`/`dispatched`
- **THEN** the task's status is `cancelled`, and no further assignment, claim, or lease-renewal operation is accepted against it
### Requirement: Cancellation requests are recorded durably against in-flight tasks
The scheduler repository SHALL persist a cancellation request against an `assigned` or `dispatched` task's current attempt such that the request is observable across a control-plane process restart, before the task reaches a terminal status.
#### Scenario: Cancellation request survives a restart
- **WHEN** a cancellation request is recorded against a `dispatched` task and the control plane process restarts before the Host next renews its lease
- **THEN** the pending cancellation request is still present and is surfaced to the Host on its next renewal after restart
### Requirement: Lease renewal surfaces a pending cancellation request
The scheduler repository's lease-renewal operation SHALL report whether the renewing attempt has a pending cancellation request, without altering the normal lease-extension outcome.
#### Scenario: Renewal on a task with a pending cancellation
- **WHEN** the owning host renews the lease for an attempt that has a pending cancellation request
- **THEN** the lease is extended normally and the renewal result additionally indicates the pending cancellation
#### Scenario: Renewal on a task without a pending cancellation
- **WHEN** the owning host renews the lease for an attempt with no pending cancellation request
- **THEN** the lease is extended normally and the renewal result indicates no pending cancellation