elsa-core/test/integration/Elsa.Bpmn.Interchange.IntegrationTests/Assets/publish-gate-process.bpmn
Sipke Schoorstra 457e2a6185
feat(bpmn): publish-time validation gate for unbound tasks (#7958)
* feat(bpmn): refuse to publish a definition with an unbound BPMN task

BpmnWorkBinder already refuses an UnboundTask with no elsa:activityBinding
at import - that is the first net. A definition can be edited after
import, through Elsa's own designer rather than the BPMN document, and
that edit can remove the activity a task was bound to without touching
the document snapshot the scope still carries. ValidateBpmnProcessBindings
is the second net: a WorkflowDefinitionValidating handler that walks the
materialized workflow graph (not the inert BpmnProcessDefinition snapshot
or the stored BPMN source, both of which a graph-only edit leaves
untouched) and fails publication for any task-family element whose
binding no longer resolves to an activity in the graph, naming the
offending element id.

Import-time Dropped/Degraded findings from BpmnImportAnalysis are not
persisted anywhere a publish-time handler can reach, and BpmnImportIssue
carries no field distinguishing a Dropped finding that changes executable
meaning from one that does not; WorkflowValidationError has no severity
concept either. Extending this gate to those findings would mean guessing
at a classification the library does not expose, so it is left alone -
see the delivery notes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(bpmn): refuse to publish a BPMN task bound to a missing activity type

The publish gate only checked that a binding pointed at some activity id;
it did not check the activity actually resolved, so a binding left
pointing at an uninstalled type passed the gate and failed at run time
instead. Report that case distinctly from "not bound at all", name the
containing BpmnProcess node (not the BPMN element id) as the error's
ActivityId to match how the rest of the codebase reports it, and prove
the BpmnProcess-inside-Flowchart graph-walk with a dedicated test. Also
de-duplicate the publish-gate test fixture's binding helpers by deriving
from BpmnBindingTestBase instead of re-declaring them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(bpmn): filter the publish gate's loops explicitly

Replace the implicit-filter foreach/continue pattern in
ValidateBpmnProcessBindings with .OfType/.Where so each loop only
iterates the elements it acts on, without changing behaviour, error
messages, or error ordering.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 23:25:31 +02:00

29 lines
1.4 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:elsa="https://elsaworkflows.io/schemas/bpmn/v1"
id="Definitions_publish-gate-process"
targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="publish-gate-process" name="Publish Gate Process" isExecutable="true">
<bpmn:startEvent id="Start_1">
<bpmn:timerEventDefinition>
<bpmn:timeCycle>R/PT1H</bpmn:timeCycle>
</bpmn:timerEventDefinition>
<bpmn:outgoing>Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="NotifyWarehouse" name="Notify Warehouse">
<bpmn:extensionElements>
<elsa:activityBinding activityType="Elsa.WriteLine">
<elsa:input name="text">{"typeName":"String","expression":{"type":"Literal","value":"Notifying the warehouse"}}</elsa:input>
</elsa:activityBinding>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1</bpmn:incoming>
<bpmn:outgoing>Flow_2</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="End_1">
<bpmn:incoming>Flow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1" sourceRef="Start_1" targetRef="NotifyWarehouse" />
<bpmn:sequenceFlow id="Flow_2" sourceRef="NotifyWarehouse" targetRef="End_1" />
</bpmn:process>
</bpmn:definitions>