2026-06-20 22:16:07 +08:00
|
|
|
|
"""Execution 子域 Router。
|
|
|
|
|
|
|
|
|
|
|
|
提供执行记录的查询 / 详情 / trace / 重试 / 清理 / 统计聚合 /
|
|
|
|
|
|
慢执行 / 重试链 / 执行上下文 / 按 trace 查询 API。挂载到
|
|
|
|
|
|
``/system/external-systems/executions`` 前缀下(根前缀由聚合 router 追加)。
|
|
|
|
|
|
|
|
|
|
|
|
路径顺序约束:静态路径 ``/cleanup`` / ``/stats`` / ``/stats/grouped`` /
|
|
|
|
|
|
``/stats/error-analysis`` / ``/stats/trend`` / ``/slow`` / ``/by-trace/{trace_id}``
|
|
|
|
|
|
必须在 ``/{execution_id}`` 前声明,避免被路径参数捕获。
|
|
|
|
|
|
|
|
|
|
|
|
设计依据:docs/vibe/v1.1/设计方案/2026-06-17-Router-API-设计方案.md
|
|
|
|
|
|
扩展依据:docs/vibe/v1.1/设计方案/RouterAPI扩展设计/10-execution_router扩展设计方案.md
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from typing import Any, Literal
|
|
|
|
|
|
|
2026-07-11 21:39:05 +08:00
|
|
|
|
from fastapi import APIRouter, Depends, Path, Query
|
2026-06-20 22:16:07 +08:00
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
|
|
|
|
|
from yuxi.external_systems.use_cases.dto.execution import (
|
|
|
|
|
|
CleanupInput,
|
2026-07-11 21:39:05 +08:00
|
|
|
|
ExecutionStatusLiteral,
|
2026-06-20 22:16:07 +08:00
|
|
|
|
GetExecutionContextInput,
|
|
|
|
|
|
GetExecutionDetailInput,
|
|
|
|
|
|
GetExecutionStatsInput,
|
|
|
|
|
|
GetExecutionTraceInput,
|
|
|
|
|
|
GetExecutionTrendInput,
|
|
|
|
|
|
GetSlowExecutionsInput,
|
|
|
|
|
|
GetTraceExecutionsInput,
|
|
|
|
|
|
ListRetriesInput,
|
|
|
|
|
|
PaginateExecutionsInput,
|
|
|
|
|
|
RetryExecutionInput,
|
|
|
|
|
|
)
|
|
|
|
|
|
from yuxi.storage.postgres.models_business import User
|
|
|
|
|
|
|
|
|
|
|
|
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
|
|
|
|
|
|
|
|
|
|
|
|
execution_router = APIRouter(prefix="/executions", tags=["external-systems-execution"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# === Request Schemas ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CleanupExecutionsRequest(BaseModel):
|
|
|
|
|
|
"""清理历史执行记录请求体。字段对齐 ``CleanupInput``。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
2026-07-11 21:39:05 +08:00
|
|
|
|
system_id: int | None = Field(default=None, ge=1)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
before_at: datetime | None = None
|
2026-07-11 21:39:05 +08:00
|
|
|
|
status: ExecutionStatusLiteral | None = None
|
2026-06-20 22:16:07 +08:00
|
|
|
|
batch_size: int = Field(default=1000, ge=1, le=10000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# === 静态路径端点(必须在 /{execution_id} 之前声明) ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.post("/cleanup", response_model=dict)
|
|
|
|
|
|
async def cleanup_old_executions(
|
|
|
|
|
|
payload: CleanupExecutionsRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""清理历史执行记录(管理员)。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = CleanupInput(
|
|
|
|
|
|
system_id=payload.system_id,
|
|
|
|
|
|
before_at=payload.before_at,
|
|
|
|
|
|
status=payload.status,
|
|
|
|
|
|
batch_size=payload.batch_size,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.cleanup_old_executions(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("", response_model=dict)
|
|
|
|
|
|
async def paginate_executions(
|
|
|
|
|
|
limit: int = Query(100, ge=1, le=500),
|
|
|
|
|
|
offset: int = Query(0, ge=0),
|
2026-07-04 00:16:45 +08:00
|
|
|
|
system_id: int | None = Query(None, ge=1),
|
|
|
|
|
|
env_key: str | None = Query(None, max_length=32),
|
|
|
|
|
|
tool_slug: str | None = Query(None, max_length=128),
|
2026-07-11 21:39:05 +08:00
|
|
|
|
status: ExecutionStatusLiteral | None = Query(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
None, description="执行状态:pending/running/success/failed/timeout/throttled/cancelled"
|
|
|
|
|
|
),
|
|
|
|
|
|
caller: str | None = Query(None, max_length=32),
|
|
|
|
|
|
caller_id: str | None = Query(None, max_length=64, description="按调用方实体过滤"),
|
|
|
|
|
|
operation: str | None = Query(None, max_length=32, description="按操作类型过滤"),
|
|
|
|
|
|
tag_key: str | None = Query(None, max_length=64, description="按业务标签键过滤"),
|
|
|
|
|
|
tag_value: str | None = Query(None, max_length=256, description="按业务标签值过滤"),
|
|
|
|
|
|
trace_id: str | None = Query(None, max_length=64),
|
|
|
|
|
|
correlation_id: str | None = Query(None, max_length=64),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
start_time: datetime | None = Query(None, description="ISO8601 开始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="ISO8601 结束时间"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""分页查询执行记录。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = PaginateExecutionsInput(
|
2026-07-11 06:57:24 +08:00
|
|
|
|
limit=limit,
|
|
|
|
|
|
offset=offset,
|
2026-06-20 22:16:07 +08:00
|
|
|
|
system_id=system_id,
|
|
|
|
|
|
env_key=env_key,
|
|
|
|
|
|
tool_slug=tool_slug,
|
|
|
|
|
|
status=status,
|
|
|
|
|
|
caller=caller,
|
|
|
|
|
|
caller_id=caller_id,
|
|
|
|
|
|
operation=operation,
|
|
|
|
|
|
tag_key=tag_key,
|
|
|
|
|
|
tag_value=tag_value,
|
|
|
|
|
|
trace_id=trace_id,
|
|
|
|
|
|
correlation_id=correlation_id,
|
|
|
|
|
|
start_at=start_time,
|
|
|
|
|
|
end_at=end_time,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.paginate_executions(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# === 扩展:统计聚合 / 慢执行 / 按 trace 查询(静态路径,必须在 /{execution_id} 之前) ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/stats", response_model=dict)
|
|
|
|
|
|
async def get_execution_stats(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
system_id: int | None = Query(None, ge=1, description="系统过滤"),
|
|
|
|
|
|
env_key: str | None = Query(None, max_length=32, description="环境过滤"),
|
|
|
|
|
|
adapter_type: str | None = Query(None, max_length=32, description="适配器类型过滤"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
start_time: datetime | None = Query(None, description="起始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="结束时间"),
|
|
|
|
|
|
tool_limit: int = Query(20, ge=1, le=100, description="Top 工具数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""执行统计聚合。含按状态/工具分组。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetExecutionStatsInput(
|
|
|
|
|
|
system_id=system_id,
|
|
|
|
|
|
env_key=env_key,
|
|
|
|
|
|
adapter_type=adapter_type,
|
|
|
|
|
|
start=start_time,
|
|
|
|
|
|
end=end_time,
|
|
|
|
|
|
limit=tool_limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.get_execution_stats(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/stats/grouped", response_model=dict)
|
|
|
|
|
|
async def get_grouped_stats(
|
|
|
|
|
|
group_by: Literal["tool"] = Query(..., description="分组维度,仅支持 tool"),
|
2026-07-04 00:16:45 +08:00
|
|
|
|
system_id: int | None = Query(None, ge=1, description="系统过滤"),
|
|
|
|
|
|
env_key: str | None = Query(None, max_length=32, description="环境过滤"),
|
|
|
|
|
|
adapter_type: str | None = Query(None, max_length=32, description="适配器类型过滤"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
start_time: datetime | None = Query(None, description="起始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="结束时间"),
|
|
|
|
|
|
limit: int = Query(20, ge=1, le=100, description="返回数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""分组统计。仅支持 group_by=tool(仓储仅 aggregate_by_tool 支持分组)。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetExecutionStatsInput(
|
|
|
|
|
|
system_id=system_id,
|
|
|
|
|
|
env_key=env_key,
|
|
|
|
|
|
adapter_type=adapter_type,
|
|
|
|
|
|
start=start_time,
|
|
|
|
|
|
end=end_time,
|
|
|
|
|
|
limit=limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.get_grouped_stats(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/stats/error-analysis", response_model=dict)
|
|
|
|
|
|
async def get_error_analysis(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
system_id: int = Query(..., ge=1, description="系统 ID(必填)"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
start_time: datetime | None = Query(None, description="起始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="结束时间"),
|
|
|
|
|
|
limit: int = Query(5, ge=1, le=50, description="返回数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""错误分析。按错误码分组统计失败执行数量。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetExecutionStatsInput(
|
|
|
|
|
|
system_id=system_id,
|
|
|
|
|
|
start=start_time,
|
|
|
|
|
|
end=end_time,
|
|
|
|
|
|
limit=limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.get_error_analysis(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/stats/trend", response_model=dict)
|
|
|
|
|
|
async def get_execution_trend(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
tool_slug: str = Query(..., min_length=1, max_length=128, description="工具标识(必填)"),
|
|
|
|
|
|
system_id: int | None = Query(None, ge=1, description="系统过滤"),
|
|
|
|
|
|
env_key: str | None = Query(None, max_length=32, description="环境过滤"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
start_time: datetime | None = Query(None, description="起始时间"),
|
|
|
|
|
|
end_time: datetime | None = Query(None, description="结束时间"),
|
|
|
|
|
|
interval: Literal["hour", "day"] = Query("day", description="时间桶粒度:hour / day"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""执行趋势。按时间桶 + 工具聚合。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetExecutionTrendInput(
|
|
|
|
|
|
tool_slug=tool_slug,
|
|
|
|
|
|
system_id=system_id,
|
|
|
|
|
|
env_key=env_key,
|
|
|
|
|
|
start=start_time,
|
|
|
|
|
|
end=end_time,
|
|
|
|
|
|
interval=interval,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.get_execution_trend(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/slow", response_model=dict)
|
|
|
|
|
|
async def list_slow_executions(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
system_id: int = Query(..., ge=1, description="系统 ID(必填)"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
min_duration_ms: int = Query(..., ge=0, description="耗时阈值(毫秒,必填)"),
|
|
|
|
|
|
limit: int = Query(50, ge=1, le=200, description="返回数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""慢执行查询。按耗时降序。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetSlowExecutionsInput(
|
|
|
|
|
|
system_id=system_id,
|
|
|
|
|
|
min_duration_ms=min_duration_ms,
|
|
|
|
|
|
limit=limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.list_slow_executions(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/by-trace/{trace_id}", response_model=dict)
|
|
|
|
|
|
async def list_by_trace(
|
2026-07-11 21:39:05 +08:00
|
|
|
|
trace_id: str = Path(..., min_length=1, max_length=64, description="调用链路 ID"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
limit: int = Query(50, ge=1, le=200, description="返回数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""按 trace 查询。返回 execution + trace 组合。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetTraceExecutionsInput(
|
|
|
|
|
|
trace_id=trace_id,
|
|
|
|
|
|
limit=limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.list_by_trace(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# === 动态路径端点 /{execution_id} ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/{execution_id}", response_model=dict)
|
|
|
|
|
|
async def get_execution_detail(
|
2026-07-11 21:39:05 +08:00
|
|
|
|
execution_id: str = Path(..., min_length=1, max_length=128, description="执行记录 ID"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""获取执行详情。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetExecutionDetailInput(execution_id=execution_id)
|
|
|
|
|
|
output = await use_cases.execution_service.get_execution_detail(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/{execution_id}/trace", response_model=dict)
|
|
|
|
|
|
async def get_execution_trace(
|
2026-07-11 21:39:05 +08:00
|
|
|
|
execution_id: str = Path(..., min_length=1, max_length=128, description="执行记录 ID"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""获取执行 trace。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetExecutionTraceInput(execution_id=execution_id)
|
|
|
|
|
|
output = await use_cases.execution_service.get_execution_trace(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/{execution_id}/retries", response_model=dict)
|
|
|
|
|
|
async def list_retries(
|
2026-07-11 21:39:05 +08:00
|
|
|
|
execution_id: str = Path(..., min_length=1, max_length=128, description="执行记录 ID"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
limit: int = Query(50, ge=1, le=200, description="返回数量"),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""重试链查询。基于 retry_of 字段,按 started_at 升序。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = ListRetriesInput(
|
|
|
|
|
|
execution_id=execution_id,
|
|
|
|
|
|
limit=limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.list_retries(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.get("/{execution_id}/context", response_model=dict)
|
|
|
|
|
|
async def get_execution_context(
|
2026-07-11 21:39:05 +08:00
|
|
|
|
execution_id: str = Path(..., min_length=1, max_length=128, description="执行记录 ID"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""执行上下文。含 trace + 关联告警。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetExecutionContextInput(execution_id=execution_id)
|
|
|
|
|
|
output = await use_cases.execution_service.get_execution_context(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@execution_router.post("/{execution_id}/retry", response_model=dict)
|
|
|
|
|
|
async def retry_execution(
|
2026-07-11 21:39:05 +08:00
|
|
|
|
execution_id: str = Path(..., min_length=1, max_length=128, description="执行记录 ID"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
2026-07-11 05:40:58 +08:00
|
|
|
|
"""重试执行(管理员)。
|
|
|
|
|
|
|
|
|
|
|
|
``caller`` / ``caller_id`` 均使用当前管理员 uid,用于审计日志。
|
|
|
|
|
|
执行记录的 ``caller`` 由 service 层固定为 ``retry``,确保重试语义准确。
|
|
|
|
|
|
"""
|
2026-06-20 22:16:07 +08:00
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = RetryExecutionInput(
|
|
|
|
|
|
execution_id=execution_id,
|
|
|
|
|
|
caller=current_user.uid,
|
|
|
|
|
|
caller_id=current_user.uid,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.execution_service.retry_execution(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|