"""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 ]