* test(bpmn): event subprocesses, dormant and listener-backed (#7933) Both flavours, and the three responsibilities the host has for them. The production diff is empty: the applier and binder need no event-subprocess code path, which is what #7909 measured and what this pins. A dormant catcher -- error or escalation -- rides the FaultSignal seam and the escalation signal path, and reaches the host as an ordinary StartWork for its body. A listener-backed one gets a second StartWork for its listenerBindingRef at scope start and a CancelWorkSubtree for it when the scope completes. Both already apply like any other command. The host responsibilities, each with the failure it would otherwise hide: - The listener is armed at scope start and observable in the scope's own ledger before anything fires, not inferred from a fire that worked. - A completing scope retires a still-armed listener. Pinned twice: at the root, where Elsa's own container completion would cancel the child regardless and only the scope's ledger tells the two apart, and inside a subprocess the workflow outlives, where a listener left behind is one something could still resume into. - The start-element hint reaches the body and nothing else inherits it. The body's only start event is event-defined, so a body that never received the hint faults bpmn.start.none-available rather than starting somewhere plausible; the ordinary subprocess inside it faults bpmn.start.unresolved-hint if the hint travels where it must not. The scope's invocation correlation is read back after the body has run work of its own, because the dictionary is fixed for the scope's lifetime and the hint is read from it. - A non-interrupting listener fired twice re-arms onto the slot the first fire vacated, holding one live record and one bookmark at a time. This is the case the completed-work-removed-before-the-interpreter-is-asked ordering exists for, and it is now observable; BpmnHostInvariantTests points at it. The library's declaration rules are pinned as refusals rather than gaps: a body with more than one start event, a second error-triggered event subprocess in a scope, and a non-interrupting error event subprocess are each refused when the scope builds its graph, before any work starts. The last is additionally dropped at import, with the rest of the document reading as written -- the dropped body carries an undeclared serviceTask, so an import that still succeeds is what proves the drop took its bindings with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(bpmn): pin the catch-all escalation event subprocess refusal Two error-triggered event subprocesses per scope was pinned but its sibling rule -- at most one code-less catch-all escalation event subprocess -- had no test. Add TwoCatchAllEscalationEventSubprocesses_AreRefused, mirroring the error refusal test, and let Escalation() build a code-less definition. Also record in BpmnCommandApplier why CancelSubtreeAsync's explicit subtree cancellation is redundant on the scope-completion path (Elsa's own container-completion behaviour already covers it) while the ledger removal above it is not, so a future reader does not "simplify" the ledger removal away. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| Scenarios | ||
| BpmnFeatureTests.cs | ||
| Elsa.Bpmn.IntegrationTests.csproj | ||