feat(validation): v2 LLM dashboard validation — plugin, routes, services

Core implementation of the v2 LLM dashboard validation pipeline:
- LLM plugin with Path A (screenshots) and Path B (logs-only) execution
- Validation task management (CRUD, schedule, run)
- WebSocket task progress with Python 3.13 asyncio fix
- Cross-task runs listing (GET /validation-tasks/runs/all)
- RecordResponse schema for validation records
- JSON prompt helper, per-dashboard status aggregation
- Prompt templates with docs/git-commit/validation presets
- Migration: v2 validation models + description column
- Tests: plugin persistence, prompt templates, batch, payload, url
This commit is contained in:
2026-05-31 22:32:20 +03:00
parent 963843c2f6
commit cd2ff6c745
29 changed files with 5276 additions and 863 deletions

View File

@@ -25,6 +25,7 @@ from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.sessions import SessionMiddleware
from typing import Any
from alembic import command as alembic_command
@@ -52,8 +53,8 @@ from .api.routes import (
storage,
tasks,
translate,
validation,
)
from .api.routes.validation_tasks import router as validation_tasks
from .core.auth.security import get_password_hash
from .core.cot_logger import seed_trace_id
from .core.database import AuthSessionLocal, init_db
@@ -341,7 +342,6 @@ async def log_requests(request: Request, call_next):
# @RELATION DEPENDS_ON -> [ReportsRouter]
# @RELATION DEPENDS_ON -> [LlmRoutes]
# @RELATION DEPENDS_ON -> [CleanReleaseV2Api]
# @RELATION DEPENDS_ON -> [ValidationRoutes]
# @RELATION DEPENDS_ON -> [MaintenanceRouter]
# Include API routes
app.include_router(auth.router)
@@ -366,7 +366,7 @@ app.include_router(profile.router)
app.include_router(dataset_review.router)
app.include_router(health.router)
app.include_router(translate.router)
app.include_router(validation.router, prefix="/api/validation", tags=["Validation"])
app.include_router(validation_tasks, prefix="/api/validation-tasks", tags=["Validation Tasks"])
app.include_router(maintenance.maintenance_router)
# #endregion API_Routes
# #region api.include_routers [C:1] [TYPE Action] [SEMANTICS routes, registration, api]
@@ -590,8 +590,10 @@ async def websocket_endpoint(
# ── Main loop: listen on both log and status queues ──
while True:
log_task = asyncio.create_task(log_queue.get())
status_task = asyncio.create_task(status_queue.get())
done, _ = await asyncio.wait(
[log_queue.get(), status_queue.get()],
[log_task, status_task],
return_when=asyncio.FIRST_COMPLETED,
)
for coro in done:
@@ -634,7 +636,11 @@ async def websocket_endpoint(
await asyncio.sleep(2)
except (WebSocketDisconnect, StopIteration):
if isinstance(StopIteration):
pass # normal termination
# Task reached terminal state — close cleanly with code 1000
try:
await websocket.close(code=1000, reason="Task completed")
except Exception:
pass
logger.reason(
"WebSocket client disconnected or stream ended",
extra={"task_id": task_id},