work_graph_run_query.py 2.17 KB
"""c_work_graph_run_list (`GET /api/work-graph-runs`), the one net-new
backend capability from the `v_graph_console` SDD (2026-08-03): a
cross-task "graph run history" list for the FR-5 Planner-output-overview
panel. Every other data need on that page is served by existing
`task_query`/`artifact_query` functions -- this is the only query in this
codebase that lists `work_graph_run` rows across all tasks in a project
rather than for one `task_id` at a time, which is why it lives in its own
module instead of `task_query.py` (that module's own docstring frames it
as "built around a single task_id").
"""

_VALID_ORG_NODES = ("planner", "coder", "validator")


class InvalidParamError(Exception):
    """INVALID_PARAM -- missing project_id, org_node outside the enum, or
    limit outside [1, 100]."""


def list_work_graph_runs(conn, project_id, org_node=None, limit=20):
    if not project_id:
        raise InvalidParamError("project_id is required")
    if org_node is not None and org_node not in _VALID_ORG_NODES:
        raise InvalidParamError("org_node must be one of {}, got {!r}".format(_VALID_ORG_NODES, org_node))
    if limit is None:
        limit = 20
    try:
        limit = int(limit)
    except (TypeError, ValueError):
        raise InvalidParamError("limit must be an integer, got {!r}".format(limit))
    if limit < 1 or limit > 100:
        raise InvalidParamError("limit must be in [1, 100], got {!r}".format(limit))

    query = (
        "SELECT wgr.run_id, wgr.task_id, wgr.org_node, wgr.status, wgr.started_at, wgr.completed_at, "
        "(SELECT COUNT(*) FROM work_node_execution wne WHERE wne.run_id = wgr.run_id) AS node_count "
        "FROM work_graph_run wgr JOIN task t ON t.task_id = wgr.task_id WHERE t.project_id = ?"
    )
    params = [project_id]
    if org_node:
        query += " AND wgr.org_node = ?"
        params.append(org_node)
    query += " ORDER BY wgr.started_at DESC LIMIT ?"
    params.append(limit)

    rows = conn.execute(query, params).fetchall()
    return [
        {
            "run_id": r[0], "task_id": r[1], "org_node": r[2], "status": r[3],
            "started_at": r[4], "completed_at": r[5], "node_count": r[6],
        }
        for r in rows
    ]