HISTORY_GRAPH_UI_INTEGRATION_PLAN

History Graph UI Integration — Backend Prep Plan

Date: 2026-05-22 Branch: workflow_state_backfill Predecessors (carried, untouched): CAPTURE_WORKFLOW_EXECUTION_STATE_PLAN (Phase 1: converter + capture-time machinery) and FINISH_EXEC_STATE_PLAN (tool_execution_state schema + resolver + unified producer pass). Downstream consumer: PR #22752 — History Graph UI by @guerler. He rebases on top after this lands; he drops his backend commits. Tracking comment: posted to galaxyproject/galaxy#22710 on 2026-05-22.


Why this exists

Two parallel branches converge above the persistence boundary (same convert.py, same request.py, same _capture_workflow_tool_request_state) but diverge at where the payload lands: workflow_state_backfill puts it on a tool_execution_state row reached via three FKs (the EXEC_STATE shape); #22752 mints ToolRequest(state IS NULL) rows for workflow steps and reuses Job.tool_request_id (MINT). EXEC_STATE wins for the lower levels; mvdbeek + @jmchilton aligned in the issue thread.

Most of #22752’s backend goes away on rebase. Three pieces of it don’t — they’re orthogonal to MINT-vs-EXEC_STATE and solid on their own merits. They land on workflow_state_backfill here, before the PR opens, so reviewers see one coherent backend layer with the integration pieces already in place. The History Graph UI then rebases with one endpoint swap + a TS-guided rename.

Settled decisions

Steps

1. Cherry-pick dataset_element edges + SYNTHETIC_TOOL_IDS

2. ToolSource lookup-or-create + UQ

3. New endpoint GET /api/tool_executions/{id} + wire rename

4. Verify cleanly + open PR

Files to touch

FileStepScope
lib/galaxy/managers/history_graph.py1, 3dataset_element walker + edge; wire-rename src literal
lib/galaxy/schema/history_graph.py1, 3GraphEdge.type += "dataset_element"; GraphNode.src literal flip
lib/galaxy/model/__init__.py (ToolSource)2__table_args__ = (UniqueConstraint(...),)
lib/galaxy/model/migrations/alembic/versions_gxy/<rev>_add_tool_source_hash_source_class_uq.py2dedupe + UQ
lib/galaxy/managers/tool_source.py2new — get_or_create_tool_source
lib/galaxy/webapps/galaxy/services/jobs.py (~268)2call helper, drop hash="TODO"
lib/galaxy/webapps/galaxy/services/base.py3rename _encode_tool_request_encode_request_payload; add tool_execution_to_model
lib/galaxy/webapps/galaxy/api/tools.py (or new module)3route /api/tool_executions/{id}
lib/galaxy/schema/schema.py3ToolExecutionModel
test/unit/app/managers/test_HistoryGraphBuilder.py1, 3edge cases; tool_requesttool_execution
test/unit/app/managers/test_tool_source.py2new — helper happy path + race
test/integration/test_workflow_invocation.py (or new file)3endpoint integration
client/src/api/schema/schema.ts3regenerated

What guerler picks up on rebase (documented for handoff, not in this PR)

Out of scope

Unresolved questions