# ADR-0014: Withdrawal cancels the registratie process via a BPMN message event - **Status:** Accepted - **Date:** 2026-07-16 - **Deciders:** Respellion engineering - **Relates to:** S-11 (#12); builds on ADR-0009 (external-task worker / Workflow Client), ADR-0013 (behandel-portal wiring, the Beoordelen user task) ## Context S-11 lets a zorgprofessional withdraw a still-open registration ("trek aanvraag in"). S-11a already advances the aggregate to INGETROKKEN (domain state). But the registratie process is still running in Flowable — parked at the `Beoordelen` user task — so without a second step the withdrawn registration would linger as work for a behandelaar. The withdrawal must also **cancel the running process**. Two questions shape this sub-slice. 1. **How does the case get cancelled — in code, or in the BPMN model?** 2. **How does a withdrawal correlate to the right running process instance?** ## Decision **The BPMN models the cancellation as an interrupting message boundary event on the `Beoordelen` task; the Workflow Client correlates a `RegistratieIngetrokken` message to the task's execution.** - **Modelled in BPMN, not deleted from code.** The `Beoordelen` user task carries an interrupting message boundary event (`RegistratieIngetrokken`) that routes to a dedicated "Registratie ingetrokken" end event. The process's own model says *how* a withdrawal ends it — the Workflow Client only delivers the message; it never reaches into Flowable to delete an instance. This keeps the workflow's control flow in the workflow (§8.2) and leaves an audit trail in Flowable history (the process ended via the ingetrokken path, not a raw delete). - **Correlated by the registration's own process instance.** The aggregate records its Flowable process instance id at submit, so the `WithdrawRegistration` handler correlates directly by that id — no task lookup. The Workflow Client asks Flowable for the execution **subscribed to** the `RegistratieIngetrokken` message in that instance and delivers `messageEventReceived` to it. Targeting the subscribed execution (not the user task's execution — a message boundary event's subscription lives on its own execution) is what makes the correlation land. - **Best-effort, mirroring the beoordeling.** If no open `Beoordelen` task is found (the process has not yet parked there — the `OpenZaakAanmaken` window — or has already ended), the withdrawal still stands: the aggregate is INGETROKKEN and the werkbak filters it out regardless (S-11b). We complete the domain transition first and cancel the workflow best-effort, exactly as `BeoordeelRegistratie` completes its task best-effort. ## Consequences **Positive** - The cancellation path is visible in `registratie.bpmn`; the Workflow Client stays the only code that talks to Flowable and does not delete instances behind the model's back. - Reuses the existing task-query correlation — no new plumbing, no correlation store. - A withdrawn case leaves the werkbak (its `Beoordelen` task is cancelled), and the werkbak also filters non-open registrations as a belt-and-braces for the brief window before cancellation lands. **Negative / costs** - A withdrawal raced ahead of the process reaching `Beoordelen` (during `OpenZaakAanmaken`, seconds) finds no task to cancel, so that process instance runs on to `Beoordelen` and parks there with no one to act on it (it is hidden from the werkbak by the status filter). Acceptable for this reference at these volumes; a process-level interrupting event subprocess would close the gap and is an additive follow-up if it matters. - The Flowable message-correlation REST shape is validated live (verify-stack), not in the Workflow Client's unit tests, which stub the HTTP exchange and assert only the request shape (consistent with ADR-0009). ## Alternatives considered - **Delete the process instance from the Workflow Client** (`DELETE /runtime/process-instances/{id}`) — rejected: it cancels the case but hides the reason from the BPMN model; the "why" lives in code, not the process. The message event keeps the cancellation a first-class part of the workflow. - **Interrupting message event subprocess at process level** — more robust (correlates anytime, closing the `OpenZaakAanmaken`-race gap), but a heavier BPMN construct; deferred as an additive change if the race proves to matter.