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
StatusTransitionsservice: the guarded transition primitive plus an explicit allowed-transition map (which status may move to which), injected into the engine,TimeoutSweepandWorkflowVersionManager, so there is one way to change a status everywhere. - Extract an
InstanceRecoveryservice owningreconcileStuckInstances,recoverStalledInstancesand the shared stuck-instance finder;TimeoutSweepdelegates 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
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
Comment #3
mably commentedComment #5
mably commented