feat(cloud): add targeted task governance foundation
Tests / Test passed: 659

This commit is contained in:
2026-07-13 22:21:12 +08:00
parent a3ba94be04
commit 2cd314b183
41 changed files with 2099 additions and 15 deletions
+51 -2
View File
@@ -22,6 +22,7 @@ from cloud.auth import (
NullAuthProvider,
Principal,
)
from cloud.governance import TaskSubmissionPolicyError, enforce_user_submission_policy
from cloud.sdk.models import (
DeviceResponse,
ErrorResponse,
@@ -94,8 +95,21 @@ def create_cloud_router(
payload: TaskSubmissionRequest,
request: Request,
) -> TaskSubmissionResponse:
_authorize(request, TASKS_SUBMIT_SCOPE)
task_constraints = _build_constraints(payload.constraints)
principal = _authorize(request, TASKS_SUBMIT_SCOPE)
try:
task_constraints = _build_constraints(payload.constraints)
_validate_task_target(pool, task_constraints)
_enforce_user_policy(principal, scheduler.store, task_constraints)
except TaskSubmissionPolicyError as exc:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(exc),
) from exc
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(exc),
) from exc
try:
task_id = scheduler.submit(
goal=payload.goal,
@@ -128,6 +142,8 @@ def create_cloud_router(
attempt_count=task.attempt_count,
lease_expires_at=task.lease_expires_at,
failure_reason=task.failure_reason,
target_host_id=task.constraints.target_host_id,
target_device_id=task.constraints.target_device_id,
)
@router.get("/tasks", response_model=TaskListResponse)
@@ -158,6 +174,8 @@ def create_cloud_router(
assigned_host_id=task.assigned_host_id,
attempt_count=task.attempt_count,
failure_reason=task.failure_reason,
target_host_id=task.constraints.target_host_id,
target_device_id=task.constraints.target_device_id,
created_at=task.created_at,
)
for task in tasks
@@ -303,4 +321,35 @@ def _build_constraints(model):
return TaskConstraints(
driver_type=model.driver_type,
capability_tags=list(model.capability_tags),
target_host_id=model.target_host_id,
target_device_id=model.target_device_id,
)
def _validate_task_target(pool, constraints) -> None:
if constraints.target_device_id and not constraints.target_host_id:
raise ValueError("target_device_id requires target_host_id")
if constraints.target_host_id is None:
return
if not any(host.host_id == constraints.target_host_id for host in pool.list_hosts()):
raise ValueError(f"target host {constraints.target_host_id!r} is not known")
if constraints.target_device_id is not None and not any(
device.host_id == constraints.target_host_id
and device.device_id == constraints.target_device_id
for device in pool.list_devices()
):
raise ValueError(
f"target device {constraints.target_device_id!r} is not owned by "
f"host {constraints.target_host_id!r}"
)
def _enforce_user_policy(principal, store, constraints) -> None:
if not principal.id.startswith("user:"):
return
policy = store.get_user_submission_policy(principal.id.removeprefix("user:"))
enforce_user_submission_policy(
policy,
target_host_id=constraints.target_host_id,
target_device_id=constraints.target_device_id,
)