FINISH_EXEC_STATE_PLAN

Finish EXEC_STATE — Implementation Plan

Date: 2026-05-22 Branch: workflow_state_backfill (misnomer; ignore the name — no historical backfill, EXEC_STATE schema landing). Replaces: the STEP_STATE commit 8e2e9d0197 "Capture workflow tool-step request state" at the tip. STEP_STATE was a documented stepping stone in USING_TOOL_STATE_DESIGN_OPTIONS; it will never merge. After this plan lands, rebase so the branch reads as EXTRACT_TOOL_REQUEST_STATE (EXTRACT_TOOL_REQUEST_STATE_PLAN) → EXEC_STATE with no STEP_STATE detour. Decision context: USING_TOOL_STATE_DESIGN_OPTIONSEXEC_STATE is the north star described there. Predecessor (kept): CAPTURE_WORKFLOW_EXECUTION_STATE_PLAN — Phase 1 (the converter, the per-job validation pass, the execute-time instrumentation) is reused verbatim. Phase 2 (the WIS columns + STEP_STATE resolver shape) is what this plan replaces.


Why this exists

USING_TOOL_STATE_DESIGN_OPTIONS picked STEP_STATE as a reversible cheap prefix on the way to EXEC_STATE, deferring EXEC_STATE because its benefit (uniform Job → ExecutionState walk) was gated behind a production data migration of tool_request.request. With no historical-data concern on this branch, that gating disappears: we can stand up the EXEC_STATE schema directly, write to it for new executions, and accept that historical rows degrade exactly as they do under STEP_STATE (resolver returns None → legacy state fallback).

Settled decisions

What from the STEP_STATE commit survives, verbatim

Phase 1 of CAPTURE_WORKFLOW_EXECUTION_STATE_PLAN is decision-independent and untouched:

What changes is purely where the request-level payload lands and how the resolver finds it.

Architecture

                ┌─────────────────────────────────┐
                │  Phase 1 converter (unchanged)  │
                │  resolved exec state → request_ │
                │  internal + state enum          │
                └────────────────┬────────────────┘

              ┌──────────────────┼──────────────────┐
              │                  │                  │
              ▼                  ▼                  ▼
   Tool-request mint     Workflow exec       (future: any new
   (services/jobs.py)    (workflow/modules.py executor)
                         _capture…)
              │                  │
              └──────────────────┴── new ToolExecutionState row

                ┌────────────────────┼────────────────────┐
                ▼                    ▼                    ▼
        ToolRequest.tes_id     Job.tes_id        WorkflowInvocationStep.tes_id
                │                    │                    │
                └────────────────────┴────────────────────┘


                ┌──────────────────────────────────────┐
                │ Resolver (managers/                   │
                │  workflow_request_state.py)           │
                │ (Job | ICJ) → ToolExecutionState      │
                │ Walks Job.tool_execution_state first; │
                │ falls back to Job.tool_request.request│
                │ for historical tool-request rows;     │
                │ returns None for historical workflow. │
                └──────────────────┬───────────────────┘

                       ┌───────────┴───────────┐
                       ▼                       ▼
               History Graph             workflow/extract.py
               (managers/history_graph)  (step_inputs_by_id)

Steps

1. Migration — 28885b317f78_add_tool_execution_state.py

2. Model — lib/galaxy/model/__init__.py

3. Write side — mint a ToolExecutionState at execute time

4. Resolver — lib/galaxy/managers/workflow_request_state.py

The seam already exists (the STEP_STATE commit lifted it here). Behavior swap:

5. Consumers — minimal touch

6. Tests (rework, not rewrite)

The STEP_STATE commit landed ~13 files of test changes (per 8e2e9d0197 --stat). The bulk is reusable — only the assertions about where the payload lives change.

7. Rebase shape (do last, after green)

The current 8 commits ahead of dev:

8e2e9d0197 Capture workflow tool-step request state          ← STEP_STATE, replace
9f42e2536e Workflow extract: recover tool identity off ToolSource
6fcfc177b2 Workflow extract: tool_request_ids covers queued/grey (#7003)
cedbdd1176 Rebuild schema.
84761a9dcd Workflow extract: tool_request_ids primitive for jobless executions
7c51b8189f Polish extraction: source-neutral structured-state seam + test trim
7e7f3a9074 Use ToolRequest state for workflow extraction
67963b9b40 Tool-source identity: persist tool_id/version/dynamic_tool; slim queue_jobs message

Goal: replace 8e2e9d0197 with one or two EXEC_STATE commits. Suggested split:

Or one commit if the diff is coherent — judge after the code lands. The rebase itself is git rebase -i b8d5e2f9a1c7 plus --root is not needed (we’re sitting on top of the EXTRACT chain). Force-push only the local branch; nothing public yet.

Files to touch (checklist)

FileStatusScope
lib/galaxy/model/migrations/alembic/versions_gxy/28885b317f78_add_tool_execution_state.py✅ donerenamed + rewritten
lib/galaxy/model/__init__.pypendingdrop WIS request/request_state; add ToolExecutionState; add 3 FKs + relationships
lib/galaxy/webapps/galaxy/services/jobs.py:265 (mint site)pendingdual-write ToolExecutionState next to ToolRequest
lib/galaxy/workflow/modules.py (_capture_workflow_tool_request_state)pendingmint ToolExecutionState; link WIS + Jobs
lib/galaxy/managers/workflow_request_state.pypendingswap to uniform walk; collapse WIS branch
lib/galaxy/managers/history_graph.pypendingcollapse the two producer passes
lib/galaxy/workflow/extract.pyno changealready routes via the resolver
test/unit/app/managers/test_HistoryGraphBuilder.pypendingrewrite STEP_STATE cases against new walk
test/unit/workflows/test_modules.pypendingadd row-mint assertion to taxonomy cases
test/integration/test_workflow_invocation.pypendingadd ..._persists_tool_execution_state case

Out of scope (follow-ups, not this branch)

Unresolved questions