From 0109d14f049b9bdadc2ac6459a87708bf97f802f Mon Sep 17 00:00:00 2001 From: Nikol Georgieva Date: Wed, 9 Sep 2026 23:50:50 +0300 Subject: [PATCH] intent: fix the ResilienceBpmnTest SEND fixture self-loop, refuse next: at parse, and correct the resilience-wiring text (#7226) Three leftovers from #7165 (#7056, step resilience on a notify: service task): 1. The SEND_YAML fixture was a self-loop. It built the send step with two .replace calls: the first inserted notifyOwner carrying `onError: recordFailure, next: done`; the second replaced EVERY `onError: recordFailure, next: done` with `... next: notifyOwner`, rewriting the just-inserted send too - giving notifyOwner a `next: notifyOwner` and leaving `done` unreachable. The tests passed because the parser only checked that next: names an EXISTING step. The second replace now targets provisionApp uniquely (its PT1M cycle), so the chain is provisionApp -> notifyOwner -> done; two assertFlow assertions pin it so the loop cannot come back silently. aSendWithoutResilienceKeysEmitsNoCycle also carried a dead first .replace ("onError: notifyOwner", ...) - that text never occurs - now deleted. 2. A step whose next: is itself was accepted with no diagnostic (it names an existing step), and a generated .bpmn with a self-targeting sequence flow spins. IntentParser now refuses it at parse with a message naming the step; a new StepResilienceIntentTest.aNextTargetingItselfIsRejected covers it. This is what made the fixture bug invisible, so the guard and the fixture fix are one change. 3. The resilience-wiring text was stale in two places: engine-intent/CLAUDE.md and ResilientClassDelegateFactory's javadoc described the wiring as `new DefaultActivityBehaviorFactory(...)`, while production (BpmFlowableConfig) wires `new ResilientActivityBehaviorFactory(new ResilientClassDelegateFactory())` - the class #7165 added. Both corrected. Verified: mvn formatter:validate (BUILD SUCCESS, cache wiped); engine-intent unit suite green (1188 tests, incl. the fixed ResilienceBpmnTest and the new StepResilienceIntentTest case); IntentEngineIT green (74/74), confirming valid intents still parse and generate. engine-bpm-flowable change is javadoc-only and compiles clean. (A stale-state core-initializers flake seen mid-run - CREATE vs UPDATE on leftover H2 - passed 9/9 on a clean run; it is upstream of and unrelated to this change.) Fixes #7226 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../delegate/ResilientClassDelegateFactory.java | 2 +- components/engine/engine-intent/CLAUDE.md | 2 +- .../components/intent/parser/IntentParser.java | 12 +++++++++--- .../intent/generator/ResilienceBpmnTest.java | 12 +++++++++--- .../intent/parser/StepResilienceIntentTest.java | 11 +++++++++++ 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/components/engine/engine-bpm-flowable/src/main/java/org/eclipse/dirigible/components/engine/bpm/flowable/delegate/ResilientClassDelegateFactory.java b/components/engine/engine-bpm-flowable/src/main/java/org/eclipse/dirigible/components/engine/bpm/flowable/delegate/ResilientClassDelegateFactory.java index dca8a7f42e1..ba35344eaa8 100644 --- a/components/engine/engine-bpm-flowable/src/main/java/org/eclipse/dirigible/components/engine/bpm/flowable/delegate/ResilientClassDelegateFactory.java +++ b/components/engine/engine-bpm-flowable/src/main/java/org/eclipse/dirigible/components/engine/bpm/flowable/delegate/ResilientClassDelegateFactory.java @@ -21,7 +21,7 @@ * Creates {@link ResilientClassDelegate}s for every {@code flowable:class} service task (the shape * of Flowable's own {@code DefaultClassDelegateFactory}), so the intent DSL's {@code onError} error * routing has its conversion hook on the one path all {@code delegate:} steps run through. Wired - * into the engine by {@code BpmFlowableConfig} via a {@code DefaultActivityBehaviorFactory} + * into the engine by {@code BpmFlowableConfig} via a {@code ResilientActivityBehaviorFactory} * carrying this factory. */ public class ResilientClassDelegateFactory implements ClassDelegateFactory { diff --git a/components/engine/engine-intent/CLAUDE.md b/components/engine/engine-intent/CLAUDE.md index d2f9bc884c3..4b256842f50 100644 --- a/components/engine/engine-intent/CLAUDE.md +++ b/components/engine/engine-intent/CLAUDE.md @@ -413,7 +413,7 @@ Semantics worth knowing: - **`abortOn:` on a process = cancel the in-flight instance when the document transitions into a terminal status (BPM events wave 2).** `abortOn: { status: [4, 5], then: markVoid }` — a `-transitioned` of the trigger entity into any listed EntityStatus seed id cancels the whole running instance (pending user tasks, parked waits, armed boundary timers). Emitted as an **interrupting message event subprocess** (`` with an `isInterrupting="true"` message start on `Abort` → optional cleanup serviceTask → `terminateEventDefinition`), NOT by wrapping the main flow — chosen over the proposal's subProcess-wrap sketch because it needs no restructuring of the flat step layout and still kills everything in scope. Glue: `ProcessAbortSupport` → the `aborts` collection in `.glue` → `Abort.java.template` — a `MessageHandler` on the entity's `-transitioned` topic (the channel transitions/setters already publish) that matches the status list (`entity. == || …`) and correlates `Abort` on the instance THIS process stamped in `ProcessIds` (`ProcessStamps.idFor`, falling back to `ProcessId` for records stamped before that column existed), fail-soft. `then:` omitted or `end` = terminate; a declared `serviceTask` cleanup (setField/setRelationField) is **abort-only** — `BpmnIntentGenerator` filters it out of the main linear chain (`steps.removeIf`) and re-emits it inside the event subprocess (its setter glue is still generated by `SetFieldSupport`). Parser (`validateAbortOn`): integer `status` (scalar or list), trigger entity with a `function: EntityStatus` relation, `then` = `end`/a setField-setRelationField serviceTask that is NOT explicitly routed to from the main flow (`next`/`then`/`else`). DI: the event subprocess is a fixed-placement container box below the main lane (BPMN-2.0 expanded-subprocess children carry absolute plane coordinates). Requested/shaped in upstream discussion #6340. **Consumers:** the orphaned-Inbox-task hole (cancel a SalesOrder mid-confirm), and the structural replacement for a cancelling `expire:` guard (kf quotations drops its `custom/` guard delegate). Caveat: `then` cleanup is one serviceTask (a multi-step cleanup chain is future work). - **`whenDeleted: abort | refuse` on a process = what a DELETE of the trigger row does to the in-flight instance (#7074).** `abortOn` is deaf to deletes - a `-deleted` is not a `-transitioned` - so deleting a DRAFT under approval left its Inbox task alive, claimable, opening a form of `—`s and completable over a row that no longer existed. Now EVERY entity-triggered process gets a `AbortOnDelete` `MessageHandler` on the trigger entity's `-deleted` topic (`deleteAborts` glue collection, `AbortOnDelete.java.template`, the pipeline's collection case) that reads its own `ProcessIds` stamp off the deleted payload and `Process.cancel`s the instance if `Process.isRunning` - generated whatever `whenDeleted` says, because `refuse` guards only the REST surface and a cascade or a reaction still deletes rows. `refuse` lands on the trigger entity as the `processDeleteGuards` scalar (`: