feat(cloud-sdk): expose distributed task status
This commit is contained in:
@@ -63,7 +63,7 @@
|
||||
|
||||
## 8. Public SDK And Operational Delivery
|
||||
|
||||
- [ ] 8.1 Extend public task status models/routes with attempt count, lease expiry metadata, and terminal failure details without exposing lease credentials.
|
||||
- [x] 8.1 Extend public task status models/routes with attempt count, lease expiry metadata, and terminal failure details without exposing lease credentials.
|
||||
- [ ] 8.2 Add bearer authentication and typed authorization errors to `CloudClient` while preserving injectable HTTP clients for tests.
|
||||
- [ ] 8.3 Add container definitions and example environment configuration for the cloud API, PostgreSQL, and Host Agent without committing secrets.
|
||||
- [ ] 8.4 Document local SQLite startup, deployed PostgreSQL migration/startup, credential/scopes setup, Runtime AI Planner configuration, and shutdown/rollback procedures.
|
||||
|
||||
@@ -107,6 +107,9 @@ def create_cloud_router(
|
||||
workflow_definition_id=task.workflow_definition_id,
|
||||
assigned_device_id=task.assigned_device_id,
|
||||
assigned_host_id=task.assigned_host_id,
|
||||
attempt_count=task.attempt_count,
|
||||
lease_expires_at=task.lease_expires_at,
|
||||
failure_reason=task.failure_reason,
|
||||
)
|
||||
|
||||
@router.get("/devices", response_model=list[DeviceResponse])
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -29,6 +30,9 @@ class TaskStatusResponse(BaseModel):
|
||||
workflow_definition_id: str | None = None
|
||||
assigned_device_id: str | None = None
|
||||
assigned_host_id: str | None = None
|
||||
attempt_count: int = 0
|
||||
lease_expires_at: datetime | None = None
|
||||
failure_reason: str | None = None
|
||||
|
||||
|
||||
class DeviceResponse(BaseModel):
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from cloud.config import CloudConfig
|
||||
@@ -92,6 +94,47 @@ def test_unknown_task_id_returns_404(tmp_path) -> None:
|
||||
assert resp.status_code == 404, resp.text
|
||||
|
||||
|
||||
def test_task_status_exposes_distributed_metadata_without_lease_secret(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
app, pool, scheduler, _ = _build_app(tmp_path)
|
||||
pool.sync_host_devices(
|
||||
"host-a",
|
||||
[Device(id="device-a", driver_type="wda", status="idle")], # type: ignore[arg-type]
|
||||
)
|
||||
task_id = scheduler.submit(goal="remote task")
|
||||
scheduler.assign()
|
||||
|
||||
active = _client_for(app).get(f"/v1/tasks/{task_id}").json()
|
||||
|
||||
assert active["status"] == "assigned"
|
||||
assert active["assigned_host_id"] == "host-a"
|
||||
assert active["assigned_device_id"] == "device-a"
|
||||
assert active["attempt_count"] == 1
|
||||
assert active["lease_expires_at"] is not None
|
||||
assert active["failure_reason"] is None
|
||||
assert "lease_id" not in active
|
||||
|
||||
task = scheduler.store.get_task(task_id)
|
||||
scheduler.store.record_task_result(
|
||||
task_id=task_id,
|
||||
attempt=task.attempt_count,
|
||||
lease_id=task.lease_id or "",
|
||||
host_id=task.assigned_host_id or "",
|
||||
status="failed",
|
||||
failure_reason="planner unavailable",
|
||||
terminal_result={"runtime_status": "failed"},
|
||||
completed_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
failed = _client_for(app).get(f"/v1/tasks/{task_id}").json()
|
||||
|
||||
assert failed["status"] == "failed"
|
||||
assert failed["attempt_count"] == 1
|
||||
assert failed["failure_reason"] == "planner unavailable"
|
||||
assert "lease_id" not in failed
|
||||
|
||||
|
||||
def test_device_and_host_listing_reflect_pool_state(tmp_path) -> None:
|
||||
app, pool, _, _ = _build_app(tmp_path)
|
||||
pool.sync_host_devices(
|
||||
|
||||
Reference in New Issue
Block a user