Follow-up to #3608146 (merged), which consolidated the recovery layer's contended status flips behind one guarded transition() primitive and unified the two instance-shape recovery sweeps. That was a first step: the primitive is private to WorkflowEngine, so TimeoutSweep and WorkflowVersionManager still hand-roll their own status flips, and WorkflowEngine remains a single ~3600-line class carrying lifecycle, joins, forks, timers, incidents, dead-letter, recovery sweeps, retention, variables, subprocess and migration coordination. This issue pays down that architectural debt with a full decomposition, so the transition discipline is structural rather than conventional and each concern is testable in isolation.

Behavior-preserving refactor: no runtime behavior changes, and the full test suite must stay green throughout. Behavioral bugs found by the release-candidate audit are tracked as their own issues.

Proposed work

  • Extract a StatusTransitions service: the guarded transition primitive plus an explicit allowed-transition map (which status may move to which), injected into the engine, TimeoutSweep and WorkflowVersionManager, so there is one way to change a status everywhere.
  • Extract an InstanceRecovery service owning reconcileStuckInstances, recoverStalledInstances and the shared stuck-instance finder; TimeoutSweep delegates to it instead of duplicating.
  • Extract an IncidentManager: raise, retry, skip, cancel, fail and dead-letter, out of the engine.
  • Extract a JoinCoordinator: synchronize, arm join timeouts, and join firing.
  • Add enforcement: a test (or a PHPCS/PHPStan rule) that fails when a status flip bypasses StatusTransitions, so the pattern cannot silently regress.

Out of scope

The behavioral bug fixes surfaced by the release-candidate audit (concurrency and recovery races) are separate issues and land on their own. The Webform interaction handler's coupling to the submission save lifecycle is a related fragility better tracked as its own follow-up.

Acceptance

Every token and instance status transition goes through the shared StatusTransitions service; no hand-rolled load-then-save or ad hoc UPDATE status flip remains in the engine, the timeout sweep or the version manager; the recovery sweeps and the incident and join logic live in their own collaborators; the enforcement check is in place; and the full test suite still passes.

Issue fork orchestra-3608215

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 922179f9 on 1.x
    task: #3608215 Decompose WorkflowEngine into StatusTransitions,...
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.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.