diff --git a/ProcessMaker/BpmnEngine.php b/ProcessMaker/BpmnEngine.php
index 1a1d1133bc..fc921ab316 100644
--- a/ProcessMaker/BpmnEngine.php
+++ b/ProcessMaker/BpmnEngine.php
@@ -189,6 +189,11 @@ public function setInlineTaskExecutionEnabled(bool $enabled): void
public function scheduleInlineJob(array $job): void
{
+ // This is a single pending-job slot; replacing it would silently skip a task.
+ if ($this->nextInlineJob !== null) {
+ throw new \LogicException('An inline BPMN job is already scheduled.');
+ }
+
$this->nextInlineJob = $job;
}
diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php
index 11a8d3007f..126abeafa7 100644
--- a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php
+++ b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php
@@ -23,7 +23,9 @@
use ProcessMaker\Models\Process as Definitions;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\ProcessRequestToken as Token;
+use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface;
+use ProcessMaker\Nayra\Contracts\Bpmn\CallActivityInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\EventDefinitionInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\ProcessInterface;
@@ -194,7 +196,10 @@ private function runInlineTask(Token $token, $jobClass)
]);
}
- private function canRunInlineTask(Token $token): bool
+ /**
+ * Determine whether a task can safely reuse the current linear execution context.
+ */
+ private function canRunInlineTask(Token $token, EntityInterface $element): bool
{
$instance = $token->getInstance();
$engine = $instance->getEngine();
@@ -202,6 +207,39 @@ private function canRunInlineTask(Token $token): bool
return false;
}
+ // These execution models require isolation across requests or task instances.
+ if ($instance->getRawOriginal('process_collaboration_id')
+ || $instance->getRawOriginal('parent_request_id')
+ || $token->isMultiInstance()) {
+ return false;
+ }
+
+ // Boundary events depend on normal queued failure and event handling.
+ if ($element instanceof ActivityInterface && $element->getBoundaryEvents()->count() > 0) {
+ return false;
+ }
+
+ $process = $element->getProcess();
+ // BPMN participant metadata detects collaboration when request metadata is unavailable.
+ if ($process?->getProperty(ProcessInterface::BPMN_PROPERTY_PARTICIPANT)) {
+ return false;
+ }
+
+ // Keep parent tasks asynchronous before and after child-process execution.
+ foreach ($process?->getActivities() ?? [] as $activity) {
+ if ($activity instanceof CallActivityInterface) {
+ return false;
+ }
+ }
+
+ // Inline execution must not bypass an explicitly configured service queue.
+ if ($element instanceof ServiceTaskInterface) {
+ $configuration = json_decode($element->getProperty('config', '{}'), true) ?: [];
+ if (($configuration['queue'] ?? 'bpmn') !== 'bpmn') {
+ return false;
+ }
+ }
+
$activeTokens = collect($instance->getTokens())
->filter(fn ($currentToken) => !in_array(
$currentToken->getStatus(),
@@ -210,6 +248,7 @@ private function canRunInlineTask(Token $token): bool
))
->values();
+ // Reuse is safe only for the current token in a strictly linear state.
return $activeTokens->count() === 1
&& (string) $activeTokens->first()->getId() === (string) $token->getId();
}
@@ -224,7 +263,7 @@ public function runScripTask(ScriptTaskInterface $scriptTask, Token $token)
{
Log::info('Dispatch a script task: ' . $scriptTask->getId() . ' #' . $token->getId());
- if ($this->canRunInlineTask($token)) {
+ if ($this->canRunInlineTask($token, $scriptTask)) {
$this->runInlineTask($token, RunScriptTask::class);
return;
}
@@ -244,7 +283,7 @@ public function runServiceTask(ServiceTaskInterface $serviceTask, Token $token)
{
Log::info('Dispatch a service task: ' . $serviceTask->getId());
- if ($this->canRunInlineTask($token)) {
+ if ($this->canRunInlineTask($token, $serviceTask)) {
$this->runInlineTask($token, RunServiceTask::class);
return;
}
diff --git a/tests/Feature/Api/InlineTaskExecutionTest.php b/tests/Feature/Api/InlineTaskExecutionTest.php
new file mode 100644
index 0000000000..98a315f49b
--- /dev/null
+++ b/tests/Feature/Api/InlineTaskExecutionTest.php
@@ -0,0 +1,492 @@
+setUpMockScriptRunners();
+ InlineTaskTestService::reset();
+ InlineRevisionTestService::$duringRun = null;
+
+ $implementations = [
+ 'inline-test-service-a' => InlineTaskTestService::class,
+ 'inline-test-service-b' => InlineTaskTestServiceB::class,
+ 'inline-test-service-c' => InlineTaskTestService::class,
+ 'inline-test-parallel-service' => InlineTaskTestService::class,
+ 'inline-test-collaboration-service' => InlineTaskTestService::class,
+ 'inline-test-call-child-service' => InlineTaskTestService::class,
+ 'inline-test-mi-service' => InlineTaskTestService::class,
+ 'inline-test-mi-setup-service' => InlineTaskTestService::class,
+ 'inline-test-custom-queue-service' => InlineTaskTestService::class,
+ 'inline-test-revision-service' => InlineRevisionTestService::class,
+ ];
+
+ foreach ($implementations as $implementation => $class) {
+ $this->assertTrue(WorkflowManager::registerServiceImplementation($implementation, $class));
+ }
+
+ }
+
+ public function teardownInlineTaskExecution(): void
+ {
+ InlineRevisionTestService::$duringRun = null;
+ InlineTaskTestService::reset();
+ }
+
+ public function testSeq01RunsSequentialScriptAndServiceTasksInOneJob(): void
+ {
+ Bus::fake();
+ $request = $this->startInlineProcess('InlineSequentialScriptServiceTasks.bpmn', 'start');
+
+ $this->completeUserTask($request, 'user_task');
+ $firstJob = $this->onlyJob(RunScriptTask::class, 'script_a', $request->id);
+ $firstJob->handle();
+
+ $this->assertCount(1, $this->jobs(RunScriptTask::class, requestId: $request->id));
+ $this->assertCount(0, $this->jobs(RunServiceTask::class, requestId: $request->id));
+ $this->assertRequestData($request, [
+ 'seq_script_a' => true,
+ 'seq_script_b' => true,
+ 'seq_service_c' => true,
+ ]);
+ $this->assertRequestCompleted($request);
+ $this->assertNoRequestLocks($request);
+ }
+
+ public function testSeq02RunsDifferentServiceImplementationsInOneJob(): void
+ {
+ Bus::fake();
+ $request = $this->startInlineProcess('InlineSequentialServiceTasks.bpmn', 'start');
+
+ $this->onlyJob(RunServiceTask::class, 'service_a', $request->id)->handle();
+
+ $this->assertCount(1, $this->jobs(RunServiceTask::class, requestId: $request->id));
+ $this->assertSame(
+ ['service_a', 'service_b', 'service_c'],
+ InlineTaskTestService::elementRunsForRequest($request->id)
+ );
+ $this->assertRequestData($request, [
+ 'seq_service_a' => true,
+ 'seq_service_b' => true,
+ 'seq_service_c' => true,
+ ]);
+ $this->assertRequestCompleted($request);
+ $this->assertNoRequestLocks($request);
+ }
+
+ public function testPar01KeepsParallelBranchTasksAsynchronous(): void
+ {
+ Bus::fake();
+ $request = $this->startInlineProcess('InlineParallelScriptServiceTasks.bpmn', 'start');
+
+ $this->assertCount(2, $request->tokens()->where('status', 'ACTIVE')->get());
+ $scriptJob = $this->onlyJob(RunScriptTask::class, 'branch_script', $request->id);
+ $serviceJob = $this->onlyJob(RunServiceTask::class, 'branch_service', $request->id);
+
+ $scriptJob->handle();
+ $this->assertSame('ACTIVE', $request->refresh()->status);
+ $serviceJob->handle();
+
+ $this->assertRequestData($request, [
+ 'parallel_script' => true,
+ 'parallel_service' => true,
+ ]);
+ $this->assertRequestCompleted($request);
+ $this->assertNoRequestLocks($request);
+ }
+
+ public function testExc01KeepsInterruptingAndNonInterruptingBoundaryTasksAsynchronous(): void
+ {
+ Bus::fake();
+ $interrupting = $this->startInlineProcess(
+ 'InlineBoundaryEventScriptTask.bpmn',
+ 'start_interrupting'
+ );
+
+ $this->onlyJob(RunScriptTask::class, 'interrupting_setup', $interrupting->id)->handle();
+ $this->onlyJob(RunScriptTask::class, 'script_interrupting', $interrupting->id)->handle();
+
+ $this->assertRequestData($interrupting, ['interrupting_setup' => true]);
+ $this->assertRequestCompleted($interrupting);
+ $this->assertNoRequestLocks($interrupting);
+
+ Bus::fake();
+ $nonInterrupting = $this->startInlineProcess(
+ 'InlineBoundaryEventScriptTask.bpmn',
+ 'start_non_interrupting'
+ );
+
+ $this->onlyJob(RunScriptTask::class, 'non_interrupting_setup', $nonInterrupting->id)->handle();
+ $this->onlyJob(RunScriptTask::class, 'script_non_interrupting', $nonInterrupting->id)->handle();
+
+ $failingToken = $nonInterrupting->tokens()
+ ->where('element_id', 'script_non_interrupting')
+ ->where('status', 'FAILING')
+ ->first();
+ $this->assertNotNull($failingToken);
+ $this->assertRequestData($nonInterrupting, ['non_interrupting_setup' => true]);
+ $this->assertNoRequestLocks($nonInterrupting);
+ }
+
+ public function testExc02StopsInlineExecutionAtIntermediateCatchEvent(): void
+ {
+ Bus::fake();
+ $request = $this->startInlineProcess('InlineIntermediateEventChain.bpmn', 'start');
+
+ $this->onlyJob(RunScriptTask::class, 'script_a', $request->id)->handle();
+ $this->assertNotNull($this->activeToken($request, 'wait_for_signal'));
+ $this->assertCount(0, $this->jobs(RunScriptTask::class, 'script_b', $request->id));
+
+ (new CatchSignalEventInRequest($request->refresh(), [], 'INLINE_CONTINUE_SIGNAL'))->handle();
+ $this->onlyJob(RunScriptTask::class, 'script_b', $request->id)->handle();
+
+ $this->assertRequestData($request, [
+ 'intermediate_script_a' => true,
+ 'intermediate_script_b' => true,
+ ]);
+ $this->assertRequestCompleted($request);
+ $this->assertNoRequestLocks($request);
+ }
+
+ public function testExc03KeepsCollaborationTasksAsynchronousAcrossParticipants(): void
+ {
+ Bus::fake();
+ $participantA = $this->startInlineProcess('InlineCollaborationScriptTasks.bpmn', 'start_a');
+
+ $this->onlyJob(RunScriptTask::class, 'participant_a_script_a', $participantA->id)->handle();
+ $this->onlyJob(RunScriptTask::class, 'participant_a_script_b', $participantA->id)->handle();
+
+ $collaborationId = $participantA->refresh()->process_collaboration_id;
+ $participantB = ProcessRequest::query()
+ ->where('process_collaboration_id', $collaborationId)
+ ->whereKeyNot($participantA->id)
+ ->firstOrFail();
+
+ $this->onlyJob(RunServiceTask::class, 'participant_b_service', $participantB->id)->handle();
+ $this->onlyJob(RunScriptTask::class, 'participant_b_script', $participantB->id)->handle();
+
+ $this->assertRequestData($participantA, [
+ 'participant_a_script_a' => true,
+ 'participant_a_script_b' => true,
+ ]);
+ $this->assertRequestData($participantB, [
+ 'participant_b_service' => true,
+ 'participant_b_script' => true,
+ ]);
+ $this->assertRequestCompleted($participantA);
+ $this->assertRequestCompleted($participantB);
+ $this->assertNoRequestLocks($participantA, $participantB);
+ }
+
+ public function testExc04KeepsCallActivityAndChildRequestAsynchronous(): void
+ {
+ Bus::fake();
+ $child = $this->createInlineProcess('InlineCallActivityChild.bpmn');
+ $parent = $this->createInlineProcess('InlineCallActivityChain.bpmn', [
+ '[child_process_id]' => (string) $child->id,
+ ]);
+ $parentRequest = $this->startProcess($parent, 'start');
+
+ $this->onlyJob(RunScriptTask::class, 'script_a', $parentRequest->id)->handle();
+ $childRequest = ProcessRequest::where('parent_request_id', $parentRequest->id)->firstOrFail();
+ $this->assertCount(0, $this->jobs(RunScriptTask::class, 'script_b', $parentRequest->id));
+
+ $this->onlyJob(RunServiceTask::class, 'child_service', $childRequest->id)->handle();
+ $this->onlyJob(RunScriptTask::class, 'script_b', $parentRequest->id)->handle();
+
+ $this->assertRequestData($childRequest, ['call_child_service' => true]);
+ $this->assertRequestData($parentRequest, [
+ 'call_parent_script_a' => true,
+ 'call_child_service' => true,
+ 'call_parent_script_b' => true,
+ ]);
+ $this->assertRequestCompleted($childRequest);
+ $this->assertRequestCompleted($parentRequest);
+ $this->assertNoRequestLocks($parentRequest, $childRequest);
+ }
+
+ public function testExc05KeepsSequentialAndParallelMultiInstanceTasksAsynchronous(): void
+ {
+ Bus::fake();
+ $sequential = $this->startInlineProcess(
+ 'InlineMultiInstanceAutomatedTask.bpmn',
+ 'start_sequential',
+ ['sequential_items' => [['id' => 1], ['id' => 2], ['id' => 3]]]
+ );
+ $this->onlyJob(RunScriptTask::class, 'sequential_setup', $sequential->id)->handle();
+
+ for ($index = 0; $index < 3; $index++) {
+ $jobs = $this->jobs(RunServiceTask::class, 'sequential_service', $sequential->id);
+ $this->assertCount($index + 1, $jobs);
+ $jobs->get($index)->handle();
+ }
+
+ $this->assertSame(
+ ['sequential_service', 'sequential_service', 'sequential_service'],
+ InlineTaskTestService::elementRunsForRequest($sequential->id)
+ );
+ $this->assertRequestCompleted($sequential);
+ $this->assertNoRequestLocks($sequential);
+
+ InlineTaskTestService::reset();
+ Bus::fake();
+ $parallel = $this->startInlineProcess(
+ 'InlineMultiInstanceAutomatedTask.bpmn',
+ 'start_parallel',
+ ['parallel_items' => [['id' => 1], ['id' => 2], ['id' => 3]]]
+ );
+ $this->onlyJob(RunServiceTask::class, 'parallel_setup', $parallel->id)->handle();
+
+ $parallelJobs = $this->jobs(RunScriptTask::class, 'parallel_script', $parallel->id);
+ $this->assertCount(3, $parallelJobs);
+ $parallelJobs->each(fn (RunScriptTask $job) => $job->handle());
+
+ $this->assertCount(
+ 3,
+ $parallel->tokens()->where('element_id', 'parallel_script')->get()
+ );
+ $this->assertRequestCompleted($parallel);
+ $this->assertNoRequestLocks($parallel);
+ }
+
+ public function testErr01AttributesFailureAndDoesNotRunSubsequentTask(): void
+ {
+ Bus::fake();
+ $request = $this->startInlineProcess('InlineFailingTaskChain.bpmn', 'start');
+
+ $this->onlyJob(RunScriptTask::class, 'successful_task', $request->id)->handle();
+
+ $failingToken = $request->tokens()
+ ->where('element_id', 'failing_task')
+ ->where('status', 'FAILING')
+ ->first();
+ $this->assertNotNull($failingToken);
+ $this->assertSame('ERROR', $request->refresh()->status);
+ $this->assertTrue($request->data['successful_task_ran'] ?? false);
+ $this->assertArrayNotHasKey('must_not_run', $request->data);
+ $this->assertFalse($request->tokens()->where('element_id', 'must_not_run')->exists());
+ $this->assertCount(0, $this->jobs(RunScriptTask::class, 'must_not_run', $request->id));
+ $this->assertNoRequestLocks($request);
+ }
+
+ public function testFbk01ReloadsContextAfterExecutionRevisionChanges(): void
+ {
+ Bus::fake();
+ $request = $this->startInlineProcess('InlineRevisionMismatch.bpmn', 'start');
+ $this->completeUserTask($request, 'user_task');
+ $initialRevision = $request->refresh()->execution_revision;
+
+ InlineRevisionTestService::$duringRun = static function (string $tokenId): void {
+ $token = ProcessRequestToken::findOrFail($tokenId);
+ $request = $token->processRequest;
+ $request->data = array_merge($request->data, ['external_revision_write' => true]);
+ $request->execution_revision = (int) $request->execution_revision + 1;
+ $request->saveQuietly();
+ };
+
+ $this->onlyJob(RunServiceTask::class, 'service_a', $request->id)->handle();
+ $scriptJob = $this->jobs(RunScriptTask::class, 'script_b', $request->id)->first();
+ $scriptJob?->handle();
+
+ $this->assertRequestData($request, [
+ 'external_revision_write' => true,
+ 'revision_service_a' => true,
+ 'revision_script_b' => true,
+ ]);
+ $this->assertGreaterThan($initialRevision, $request->refresh()->execution_revision);
+ $this->assertRequestCompleted($request);
+ $this->assertNoRequestLocks($request);
+ }
+
+ public function testExc06DispatchesCustomQueueServiceInsteadOfRunningInline(): void
+ {
+ Bus::fake();
+ $request = $this->startInlineProcess('InlineCustomQueueServiceTask.bpmn', 'start');
+
+ $this->onlyJob(RunScriptTask::class, 'script_a', $request->id)->handle();
+ $serviceJob = $this->onlyJob(RunServiceTask::class, 'service_custom', $request->id);
+
+ $this->assertSame('inline-custom-queue', $serviceJob->queue);
+ $this->assertSame([], InlineTaskTestService::elementRunsForRequest($request->id));
+ $serviceJob->handle();
+
+ $this->assertRequestData($request, [
+ 'custom_queue_script_a' => true,
+ 'custom_queue_service' => true,
+ ]);
+ $this->assertRequestCompleted($request);
+ $this->assertNoRequestLocks($request);
+ }
+
+ private function startInlineProcess(string $fixture, string $startEvent, array $data = []): ProcessRequest
+ {
+ return $this->startProcess($this->createInlineProcess($fixture), $startEvent, $data);
+ }
+
+ private function createInlineProcess(string $fixture, array $replacements = []): Process
+ {
+ $bpmn = file_get_contents(__DIR__ . self::FIXTURE_PATH . $fixture);
+ $this->assertNotFalse($bpmn);
+
+ return $this->createProcess([
+ 'bpmn' => strtr($bpmn, $replacements),
+ ]);
+ }
+
+ private function activeToken(ProcessRequest $request, string $elementId): ProcessRequestToken
+ {
+ return $request->tokens()
+ ->where('element_id', $elementId)
+ ->where('status', 'ACTIVE')
+ ->firstOrFail();
+ }
+
+ private function completeUserTask(ProcessRequest $request, string $elementId): void
+ {
+ $this->completeTask($this->activeToken($request, $elementId));
+ $jobs = Bus::dispatchedSync(CompleteActivity::class);
+ $this->assertCount(1, $jobs, 'Expected the user-task completion job to be dispatched synchronously.');
+ $jobs->first()->handle();
+ }
+
+ private function jobs(string $class, ?string $elementId = null, ?int $requestId = null)
+ {
+ return Bus::dispatched($class, function ($job) use ($elementId, $requestId) {
+ $jobElementId = $job->elementId ?? null;
+ if ($elementId !== null && $jobElementId === null && isset($job->tokenId)) {
+ $jobElementId = ProcessRequestToken::find($job->tokenId)?->element_id;
+ }
+
+ return ($elementId === null || $jobElementId === $elementId)
+ && ($requestId === null || (int) $job->instanceId === $requestId);
+ })->values();
+ }
+
+ private function onlyJob(string $class, string $elementId, int $requestId)
+ {
+ $jobs = $this->jobs($class, $elementId, $requestId);
+ $this->assertCount(1, $jobs, sprintf(
+ 'Expected one %s job for request %d and element %s.',
+ class_basename($class),
+ $requestId,
+ $elementId
+ ));
+
+ return $jobs->first();
+ }
+
+ private function assertRequestData(ProcessRequest $request, array $expected): void
+ {
+ $request->refresh();
+ foreach ($expected as $key => $value) {
+ $this->assertArrayHasKey($key, $request->data);
+ $this->assertSame($value, $request->data[$key]);
+ }
+ }
+
+ private function assertRequestCompleted(ProcessRequest $request): void
+ {
+ $this->assertSame('COMPLETED', $request->refresh()->status);
+ $this->assertCount(0, $request->tokens()->where('status', 'ACTIVE')->get());
+ }
+
+ private function assertNoRequestLocks(ProcessRequest ...$requests): void
+ {
+ $requestIds = collect($requests)->pluck('id')->map(fn ($id) => (int) $id);
+ $locks = ProcessRequestLock::all()->filter(function (ProcessRequestLock $lock) use ($requestIds) {
+ $lockedIds = collect($lock->request_ids ?? [])->map(fn ($id) => (int) $id);
+
+ return $requestIds->contains((int) $lock->request_id)
+ || $lockedIds->intersect($requestIds)->isNotEmpty();
+ });
+
+ $this->assertCount(0, $locks, 'Expected BPMN request locks to be cleaned up.');
+ }
+}
+
+class InlineTaskTestService implements ServiceTaskImplementationInterface
+{
+ private static array $runs = [];
+
+ public static function reset(): void
+ {
+ self::$runs = [];
+ }
+
+ public static function elementRunsForRequest(int $requestId): array
+ {
+ return collect(self::$runs)
+ ->where('request_id', $requestId)
+ ->pluck('element_id')
+ ->values()
+ ->all();
+ }
+
+ public function run(array $data, array $config, $tokenId = '')
+ {
+ $token = ProcessRequestToken::findOrFail($tokenId);
+ self::$runs[] = [
+ 'request_id' => (int) $token->process_request_id,
+ 'element_id' => $token->element_id,
+ ];
+
+ $output = match ($token->element_id) {
+ 'service_a' => 'seq_service_a',
+ 'service_b' => 'seq_service_b',
+ 'service_c' => 'seq_service_c',
+ 'branch_service' => 'parallel_service',
+ 'participant_b_service' => 'participant_b_service',
+ 'child_service' => 'call_child_service',
+ 'sequential_service' => 'sequential_instance_service',
+ 'parallel_setup' => 'parallel_setup_service',
+ 'service_custom' => 'custom_queue_service',
+ default => 'service_' . $token->element_id,
+ };
+
+ return [$output => true];
+ }
+}
+
+class InlineTaskTestServiceB extends InlineTaskTestService
+{
+}
+
+class InlineRevisionTestService extends InlineTaskTestService
+{
+ public static $duringRun;
+
+ public function run(array $data, array $config, $tokenId = '')
+ {
+ if (self::$duringRun) {
+ (self::$duringRun)((string) $tokenId);
+ }
+
+ parent::run($data, $config, $tokenId);
+
+ return ['revision_service_a' => true];
+ }
+}
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineBoundaryEventScriptTask.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineBoundaryEventScriptTask.bpmn
new file mode 100644
index 0000000000..8419b354d8
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineBoundaryEventScriptTask.bpmn
@@ -0,0 +1,62 @@
+
+
+
+
+
+ flow_start_interrupting_setup
+
+
+ flow_start_interrupting_setup
+ flow_setup_interrupting
+ true];]]>
+
+
+ flow_setup_interrupting
+ flow_interrupting_success
+
+
+
+ flow_interrupting_boundary
+
+
+
+ flow_interrupting_success
+
+
+ flow_interrupting_boundary
+
+
+
+ flow_start_non_interrupting_setup
+
+
+ flow_start_non_interrupting_setup
+ flow_setup_non_interrupting
+ true];]]>
+
+
+ flow_setup_non_interrupting
+ flow_non_interrupting_main
+
+
+
+ flow_non_interrupting_boundary
+
+
+
+ flow_non_interrupting_main
+
+
+ flow_non_interrupting_boundary
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChain.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChain.bpmn
new file mode 100644
index 0000000000..1f2f627225
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChain.bpmn
@@ -0,0 +1,30 @@
+
+
+
+
+
+ flow_start_a
+
+
+ flow_start_a
+ flow_a_call
+ true];]]>
+
+
+ flow_a_call
+ flow_call_b
+
+
+ flow_call_b
+ flow_b_end
+ true];]]>
+
+
+ flow_b_end
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChild.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChild.bpmn
new file mode 100644
index 0000000000..6cad57bd9f
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineCallActivityChild.bpmn
@@ -0,0 +1,18 @@
+
+
+
+
+
+ flow_child_start_service
+
+
+ flow_child_start_service
+ flow_child_service_end
+
+
+ flow_child_service_end
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCollaborationScriptTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCollaborationScriptTasks.bpmn
new file mode 100644
index 0000000000..e9e37fed0a
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineCollaborationScriptTasks.bpmn
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ flow_a_start_script_a
+
+
+ flow_a_start_script_a
+ flow_a_script_a_script_b
+ true];]]>
+
+
+ flow_a_script_a_script_b
+ flow_a_script_b_send
+ true];]]>
+
+
+ flow_a_script_b_send
+ flow_a_send_end
+
+
+
+ flow_a_send_end
+
+
+
+
+
+
+
+
+
+ flow_b_start_service
+
+
+
+ flow_b_start_service
+ flow_b_service_script
+
+
+ flow_b_service_script
+ flow_b_script_end
+ true];]]>
+
+
+ flow_b_script_end
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineCustomQueueServiceTask.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineCustomQueueServiceTask.bpmn
new file mode 100644
index 0000000000..bf14ee5a39
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineCustomQueueServiceTask.bpmn
@@ -0,0 +1,24 @@
+
+
+
+
+
+ flow_start_script
+
+
+ flow_start_script
+ flow_script_service
+ true];]]>
+
+
+ flow_script_service
+ flow_service_end
+
+
+ flow_service_end
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineFailingTaskChain.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineFailingTaskChain.bpmn
new file mode 100644
index 0000000000..5111e2ae6d
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineFailingTaskChain.bpmn
@@ -0,0 +1,31 @@
+
+
+
+
+
+ flow_start_success
+
+
+ flow_start_success
+ flow_success_failure
+ true];]]>
+
+
+ flow_success_failure
+ flow_failure_later
+
+
+
+ flow_failure_later
+ flow_later_end
+ true];]]>
+
+
+ flow_later_end
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineIntermediateEventChain.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineIntermediateEventChain.bpmn
new file mode 100644
index 0000000000..cae877d0bb
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineIntermediateEventChain.bpmn
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ flow_start_a
+
+
+ flow_start_a
+ flow_a_catch
+ true];]]>
+
+
+ flow_a_catch
+ flow_catch_b
+
+
+
+ flow_catch_b
+ flow_b_end
+ true];]]>
+
+
+ flow_b_end
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineMultiInstanceAutomatedTask.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineMultiInstanceAutomatedTask.bpmn
new file mode 100644
index 0000000000..3b038e14b1
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineMultiInstanceAutomatedTask.bpmn
@@ -0,0 +1,64 @@
+
+
+
+
+
+ flow_start_sequential_setup
+
+
+ flow_start_sequential_setup
+ flow_setup_sequential
+ true];]]>
+
+
+ flow_setup_sequential
+ flow_sequential_end
+
+
+
+ sequential_items_input
+
+
+
+
+ sequential_items_input
+
+
+
+ flow_sequential_end
+
+
+
+ flow_start_parallel_setup
+
+
+ flow_start_parallel_setup
+ flow_setup_parallel
+
+
+ flow_setup_parallel
+ flow_parallel_end
+
+
+
+ parallel_items_input
+
+
+
+
+ parallel_items_input
+
+ true];]]>
+
+
+ flow_parallel_end
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineParallelScriptServiceTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineParallelScriptServiceTasks.bpmn
new file mode 100644
index 0000000000..95597d0ac8
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineParallelScriptServiceTasks.bpmn
@@ -0,0 +1,37 @@
+
+
+
+
+
+ flow_start_split
+
+
+ flow_start_split
+ flow_split_script
+ flow_split_service
+
+
+ flow_split_script
+ flow_script_join
+ true];]]>
+
+
+ flow_split_service
+ flow_service_join
+
+
+ flow_script_join
+ flow_service_join
+ flow_join_end
+
+
+ flow_join_end
+
+
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineRevisionMismatch.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineRevisionMismatch.bpmn
new file mode 100644
index 0000000000..0345456413
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineRevisionMismatch.bpmn
@@ -0,0 +1,29 @@
+
+
+
+
+
+ flow_start_user
+
+
+ flow_start_user
+ flow_user_service
+
+
+ flow_user_service
+ flow_service_script
+
+
+ flow_service_script
+ flow_script_end
+ true];]]>
+
+
+ flow_script_end
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineSequentialScriptServiceTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialScriptServiceTasks.bpmn
new file mode 100644
index 0000000000..e131c2812a
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialScriptServiceTasks.bpmn
@@ -0,0 +1,35 @@
+
+
+
+
+
+ flow_start_user
+
+
+ flow_start_user
+ flow_user_a
+
+
+ flow_user_a
+ flow_a_b
+ true];]]>
+
+
+ flow_a_b
+ flow_b_c
+ true];]]>
+
+
+ flow_b_c
+ flow_c_end
+
+
+ flow_c_end
+
+
+
+
+
+
+
+
diff --git a/tests/Feature/Api/processes/inline-task-execution/InlineSequentialServiceTasks.bpmn b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialServiceTasks.bpmn
new file mode 100644
index 0000000000..5664694f85
--- /dev/null
+++ b/tests/Feature/Api/processes/inline-task-execution/InlineSequentialServiceTasks.bpmn
@@ -0,0 +1,28 @@
+
+
+
+
+
+ flow_start_a
+
+
+ flow_start_a
+ flow_a_b
+
+
+ flow_a_b
+ flow_b_c
+
+
+ flow_b_c
+ flow_c_end
+
+
+ flow_c_end
+
+
+
+
+
+
+