Problem/Motivation

The migration surfaces preview a migration the manager will then refuse, and one of them builds the operator's mapping form out of another workflow's nodes to do it.

1. The form never checks the version belongs to the workflow. orchestra_ui.workflow_migrate carries the target version in the path, gated on administer orchestra and a \d+ pattern and nothing else. WorkflowMigrateForm::buildForm() loads that version, previews the migration and, for every live node the version has no counterpart for, renders a select whose options are the version's own nodes. Point it at a version of a different workflow and the page reports how many of this workflow's runs would move, then asks the operator to map every one of their live nodes onto the other workflow's node ids and labels. Submitting it reaches WorkflowVersionManager::requireDefinition(), which refuses it correctly, so the operator is told the version belongs elsewhere only after filling in a form that could never have worked. The manager's own docblock says the version id is operator-supplied "on the Drush surface, which is the one place it is not derived from the run itself"; this route is a second such place, and OrchestraVersionCommands::migrateVersions() already refuses a foreign version up front with a message naming both workflows.

2. A run that pinned no version cannot have its arcs remapped. A token carries the flow it arrived on, and a synchronizing join counts arcs by those ids, so a migration remaps the flow along with the node: planMigration() looks up the arc in the source definition and finds the target's arc between the same two nodes. It reads the source as getDefinition((int) $instance->getDefinitionVersion()), and an instance that pins nothing answers NULL there, which casts to 0, asks for revision 0 and gets nothing back. The source of an unpinned run is the live workflow, which is what getEffectiveDefinition() answers with everywhere else in the engine. So with versioning set to manual, a run started while versioning was off, following the live shape, cannot be migrated onto a published version whose arc ids differ, even where the arc plainly still connects the same two nodes: the migration throws instead. The message it throws blames the target ("which target version %d neither has nor connects the same two nodes as") when the cause is that the source was never read.

3. The preview does not ask the question the migration answers. previewMigration() checks nodes only, so a run whose arc cannot be remapped is previewed as ready and reports no unmapped node. Both surfaces then offer a migration that aborts on the first such instance, and the form reports it as a partial run.

Proposed resolution

  • Refuse a foreign version where the form is built, saying which workflow it belongs to, as the Drush command does.
  • Read an unpinned instance's source definition as the live workflow, so its arcs are remapped like any other run's.
  • Have the preview ask the arc question too, so an unmappable arc is reported before the operator is offered the migration.

Remaining tasks

None.

AI-Generated: Yes (Claude Code was used to help draft this issue summary and to write the code and tests on the merge request. I reviewed and ran the work myself before posting it.)

Issue fork orchestra-3622388

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review

  • mably committed 5665fb78 on 1.x
    fix: #3622388 Refuse a version migration the manager cannot place,...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.