Skip to content

fix(intent): a process trigger records ProcessId before anything else can fail (#6815) - #6855

Merged
delchev merged 1 commit into
masterfrom
fix/trigger-process-id-writeback
Aug 19, 2026
Merged

fix(intent): a process trigger records ProcessId before anything else can fail (#6815)#6855
delchev merged 1 commit into
masterfrom
fix/trigger-process-id-writeback

Conversation

@delchev

@delchev delchev commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Closes #6815.

The generated process trigger started the instance FIRST and recorded ProcessId SECOND, with independent commits between - and the guard against starting a second instance IS that stamped ProcessId. A failure in the gap left a running instance the record does not know about, and the next qualifying event started another one.

Two things came out of reading the path, beyond what the issue reported:

  • The gap was not hypothetical - it was the normal case for a whole class of process. Flowable runs a process with no wait state to its END inside Process.start, and the trigger's very next calls were Process.setVariable(processId, …), which validates the id against the RUNTIME executions and throws IllegalArgumentException for a finished instance. So such a trigger never recorded a ProcessId at all - the listener dispatcher swallowed it - and an onUpdate trigger then started one instance per event.
  • updateProperty really was refusable. The generated repository overrides it for a checks:-bearing entity and runs enforceChecks on the post-write row, which reads only the CURRENT status - so an item-less or unbalanced document in the gated status refused the write-back of a process it was already running.

The shape now

Everything that can happen before the start does. A minted businessKeyStrategy value is persisted BEFORE Process.start (the instance is started with that key, so failing to store it must prevent the start, not follow it), and every process variable - the __entityUrl/__entityId locators, the FK locators, __personalUser - rides the start payload instead of a post-start setVariable. All of them were known up front; a task created inside start now has its locators from its first moment, and a wait-state-less process no longer fails on a variable set against an instance that already finished.

The write-back can no longer be refused. It stays on the targeted updateProperty path, so the entity's own bookkeeping still runs - the history: trail entry attributed to SYSTEM, the stored label: Name - but the generated repository now runs its checks: gate only for a write that touches an authored column (SYSTEM_PROPERTIES / touchesAuthoredColumn, today ProcessId). Such a write cannot move the document into a gated status nor break what the gate checks, and by then its instance is running: an unbalanced document that records its process beats one that does not. Everything a person or a workflow authors keeps the gate, unchanged.

(I first wrote this as an un-gated JavaRepository.updateSystemProperty bypass. Rebasing onto master showed why that is wrong: the history: trail records exactly this write as the SYSTEM attribution a supervisory audit asks about, and a bypass silently dropped it - IntentEmissionCoverageIT caught it. The gate is the only thing that had to go, so the gate is the only thing that went.)

And the residual failure is loud and clean. A swallowed start (BpmProviderFlowable.startProcess logs and returns null) is reported instead of written back as a null id; if the write returns 0 rows (the record was deleted in the meantime) or throws, the trigger logs it and cancels the just-started instance - a new Process.cancel SDK delegate over the existing BpmFacade.deleteProcess - then re-throws. The cancel is deliberately tolerant: a wait-state-less instance has already finished and there is nothing left to cancel, and a cancellation problem must not replace the failure that led there.

A hard crash between the two commits can still orphan an instance; that is inherent without a distributed transaction, and this module's consistency model is checks + compensation, not 2PC. The issue's third suggestion - a platform-level reconciliation query - is not expressible here: the instance-to-record mapping lives in the generated app, not in the platform, so a generic sweep has nothing to join on. It belongs in the intent DSL if we want it.

Verification

  • IntentEngineIT (emission): the ordering (the minted key is persisted before Process.start), the whole variable map riding Json.stringify(variables) with no post-start setVariable, the null-id guard, and the cancel-and-re-throw path.
  • IntentEmissionCoverageIT (emission + published app): the conditional gate in the checks-bearing repository, and at runtime that a record created over REST comes back carrying a stamped ProcessId - the same write whose SYSTEM attribution the history-trail assertion above it depends on. Green locally (127 s), together with the existing personal-assignee inbox assertion that only passes if __personalUser really rides the start payload.
  • Both rendered template variants (with/without a business key, with/without a personal assignee, with FK locators) compiled with javac against the real SDK and JavaRepository.
  • Unit tests + release-profile javadoc green on the touched modules; formatter:validate clean.

Docs: dirigible.io PR updates the trigger section (it still described an @Listener and said only "writes the instance id back").

🤖 Generated with Claude Code

… can fail (#6815)

The stamped ProcessId is the guard that keeps a process starting at most
once, and it is written AFTER the start, in a separate transaction. A
failure in that gap left a running instance the record does not know about
- invisible to the application, while its user tasks still reached an
inbox - and the next qualifying event started another one.

The gap is now as small as it can be, and no longer silent:

- Everything that can happen before the start does. A minted
  businessKeyStrategy value is persisted BEFORE Process.start (the instance
  is started with that key), and every process variable - the
  __entityUrl/__entityId locators, the FK locators, __personalUser - rides
  the start payload instead of a post-start setVariable. They were all
  known up front, and the post-start calls were a live failure mode of
  their own: a process without a wait state runs to its end inside
  Process.start, and setVariable against a finished instance throws
  IllegalArgumentException - so such a trigger never recorded a ProcessId
  at all, and an onUpdate trigger then started an instance per event.

- The write-back can no longer be REFUSED. It stays on the targeted
  updateProperty path, so the entity's own bookkeeping still runs (the
  history: trail entry attributed to SYSTEM, the stored label: Name), but
  the generated repository now runs its checks: gate only for a write that
  touches an AUTHORED column. A write carrying nothing but platform-owned
  columns - SYSTEM_PROPERTIES, today the trigger's ProcessId - cannot move
  the document into a gated status nor break what the gate checks, and by
  then its process instance is already running: an unbalanced or item-less
  document that records its process beats one that does not.

- If the write still does not land - a swallowed start returning null, the
  row deleted meanwhile, or the write throwing - the trigger logs it and
  CANCELS the just-started instance (new Process.cancel SDK delegate),
  re-throwing the failure. An untracked live instance is the worst
  available outcome.

Verified on the emission oracles plus the published app: IntentEngineIT
asserts the ordering, the start payload and the cancel path;
IntentEmissionCoverageIT asserts the conditional gate in a checks-bearing
repository and, at runtime, that a created record carries a stamped
ProcessId - which also keeps the history trail's SYSTEM attribution of
that very write.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev force-pushed the fix/trigger-process-id-writeback branch from 04ea008 to 533cbbb Compare August 19, 2026 13:31
@delchev
delchev merged commit d60912d into master Aug 19, 2026
10 checks passed
@delchev
delchev deleted the fix/trigger-process-id-writeback branch August 19, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Process trigger starts the instance before recording ProcessId - a failed write-back yields untracked and duplicate instances

1 participant