Record the escalation-via-external-worker decision (ADR-0015, from proposal #98) and add the S-14 demo walkthrough that fires the 14-day timer early via Flowable's management API to observe the reassignment to teamlead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
4.7 KiB
Markdown
78 lines
4.7 KiB
Markdown
# ADR-0015: Beoordeling escalation reassigns via an external-worker task
|
|
|
|
- **Status:** Accepted
|
|
- **Date:** 2026-07-17
|
|
- **Deciders:** Respellion engineering
|
|
- **Relates to:** S-14 (#15); proposal #98. Builds on ADR-0009 (external-task worker / Workflow
|
|
Client), ADR-0013 (behandel-portal wiring, the `Beoordelen` user task), ADR-0014 (the boundary-event
|
|
pattern on `Beoordelen`).
|
|
|
|
## Context
|
|
|
|
S-14 escalates a beoordeling that a behandelaar does not pick up in time: after 14 days the case must
|
|
move to the `teamlead` role (PRD §5, flow 5). The `Beoordelen` user task already exists, claimable by
|
|
the `behandelaar` candidate group; the teamlead role is seeded in the medewerker realm.
|
|
|
|
Two forces shape this.
|
|
|
|
1. **The task must stay open.** Escalation changes *who may claim* an unclaimed beoordeling, not the
|
|
work itself — so the timer must be **non-interrupting**: the `Beoordelen` task keeps running while
|
|
escalation happens alongside it.
|
|
2. **Reassigning an open task's candidate group needs code.** Flowable cannot rewrite the candidate
|
|
groups of an already-open user task from BPMN XML alone — that requires either a Java delegate/listener
|
|
embedded in the engine, or an out-of-process actor driving the REST API. The repository has held a
|
|
"stock Flowable image, no custom jars; the Workflow Client is the only code that talks to Flowable
|
|
(§8.2)" posture since ADR-0009.
|
|
|
|
## Decision
|
|
|
|
**A non-interrupting `P14D` boundary timer on `Beoordelen` fires an external-worker task
|
|
(`BeoordelingEscaleren`); the Workflow Client reassigns the still-open `Beoordelen` task from the
|
|
behandelaar group to teamlead.**
|
|
|
|
- **Modelled in BPMN, driven by an external worker.** The timer routes a parallel token to an
|
|
`external-worker` service task on the `BeoordelingEscaleren` topic, ending at a dedicated "Beoordeling
|
|
geëscaleerd" end event. The model owns *when* escalation happens; the Workflow Client — the only code
|
|
that talks to Flowable (§8.2) — owns *how* the reassignment is applied, exactly as `OpenZaakAanmaken`
|
|
delegates the ZGW call (ADR-0009). No custom code runs inside Flowable.
|
|
- **Reassignment is a candidate-group swap.** The escalation worker finds the still-open `Beoordelen`
|
|
task in the escalating instance (task query by `processInstanceId` + `taskDefinitionKey`), adds
|
|
`teamlead` as a candidate group via the task identity links, then removes `behandelaar`. The task now
|
|
belongs to the teamlead; its history and variables are untouched.
|
|
- **Best-effort, mirroring beoordeling and withdrawal.** If the task is no longer open — the behandelaar
|
|
completed it in the window before the timer fired — the reassignment is a no-op. A failed reassignment
|
|
leaves the escalation job un-completed so Flowable redelivers it (§8.6), consistent with the
|
|
`OpenZaakAanmaken` worker.
|
|
- **Segregated interface.** The escalation methods live on `IBeoordelingEscalatieClient`, separate from
|
|
the `OpenZaakAanmaken` worker's `IExternalWorkerClient`, so the OpenZaak worker never sees escalation
|
|
(interface segregation). Both are implemented by the one `FlowableWorkflowClient`.
|
|
|
|
## Consequences
|
|
|
|
**Positive**
|
|
|
|
- The escalation trigger is visible in `registratie.bpmn`; Flowable stays a stock image, and the
|
|
Workflow Client remains the sole Flowable client (§8.2 upheld, not bent).
|
|
- Reuses the external-worker mechanics (topic acquire/complete, hosted pump, per-tick scope,
|
|
redelivery-on-failure) wholesale — the new code is one client capability, one processor, one pump.
|
|
- Escalation latency is bounded by the worker's poll interval (seconds) — negligible against a 14-day
|
|
timer.
|
|
|
|
**Negative / costs**
|
|
|
|
- Escalation is two REST hops (add teamlead, remove behandelaar) rather than one atomic update; between
|
|
them the task is briefly claimable by both groups. Harmless at these volumes, and the pair is idempotent
|
|
on redelivery.
|
|
- The Flowable identity-link and management-job REST shapes are validated live (verify-domain fires the
|
|
timer early via the management API), not in the Workflow Client's unit tests, which stub the HTTP
|
|
exchange and assert only the request shape — consistent with ADR-0009 and ADR-0014.
|
|
|
|
## Alternatives considered
|
|
|
|
- **Flowable timer/task listener (Java delegate).** Reassign in-engine when the timer fires. Rejected:
|
|
it needs a custom jar in Flowable, breaking the stock-image, REST-only posture and adding a build/deploy
|
|
surface to the engine for no capability the external-worker route lacks.
|
|
- **Interrupting timer that re-creates the task for teamlead.** Cancel `Beoordelen` and start a fresh
|
|
teamlead task. Rejected: it loses the task's identity/history and complicates correlation, where a
|
|
candidate-group swap on the same task expresses "the same work, now the teamlead's" directly.
|