docs(workflow): ADR-0015 + demo note for beoordeling escalation (S-14, refs #15)

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>
This commit is contained in:
2026-07-17 09:13:53 +02:00
parent 6457aad6db
commit cb8568c36a
2 changed files with 122 additions and 0 deletions

View File

@@ -298,3 +298,48 @@ interrupting boundary event ends it → the werkbak drops the case.
> DigiD submit → trek aanvraag in → ingetrokken is the Playwright happy path
> (`tests/e2e/withdrawal.spec.ts`); the owner-scoping + workflow cancellation are covered by the
> `Een registratie intrekken` acceptance scenarios and the domain live check.
## S-14 — Beoordeling escalation: 14 days unclaimed → teamlead (#15, ADR-0015)
A beoordeling a behandelaar does not pick up within 14 days escalates to the teamlead. A
non-interrupting boundary timer on the `Beoordelen` task fires a `BeoordelingEscaleren` external task;
the domain's escalation worker reassigns the still-open task's candidate group from `behandelaar` to
`teamlead`, so it moves from the behandelaar werkbak into the teamlead's. The `Beoordelen` task keeps
its identity throughout — only who may claim it changes.
The timer is 14 days, so the demo fires it early through Flowable's management API (exactly what the
verify-domain check automates):
```bash
# 1. Submit at the self-service portal (http://localhost:8140/, jan-burger / test123). The case
# parks at Beoordelen, visible in the behandelaar werkbak (http://localhost:8142/, merel-behandelaar)
# but NOT claimed.
#
# 2. Find the parked instance and its Beoordelen task, then fire the boundary timer early:
FL=http://localhost:8090/flowable-rest/service
PID=$(curl -s -u rest-admin:test -X POST "$FL/query/tasks" -H 'Content-Type: application/json' \
-d '{"processDefinitionKey":"registratie","taskDefinitionKey":"Beoordelen"}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["processInstanceId"])')
TID=$(curl -s -u rest-admin:test -X POST "$FL/query/tasks" -H 'Content-Type: application/json' \
-d '{"processDefinitionKey":"registratie","taskDefinitionKey":"Beoordelen"}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["id"])')
TJ=$(curl -s -u rest-admin:test "$FL/management/timer-jobs?processInstanceId=$PID" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["id"])')
curl -s -u rest-admin:test -X POST "$FL/management/timer-jobs/$TJ" \
-H 'Content-Type: application/json' -d '{"action":"move"}'
AJ=$(curl -s -u rest-admin:test "$FL/management/jobs?processInstanceId=$PID" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["id"])')
curl -s -u rest-admin:test -X POST "$FL/management/jobs/$AJ" \
-H 'Content-Type: application/json' -d '{"action":"execute"}'
#
# 3. Within a couple of poll cycles the task's candidate group flips to teamlead:
curl -s -u rest-admin:test "$FL/runtime/tasks/$TID/identitylinks" # → [{"group":"teamlead","type":"candidate"}]
```
**The path:** BPMN non-interrupting `P14D` boundary timer on `Beoordelen``BeoordelingEscaleren`
external task → domain escalation worker (`BeoordelingEscalatiePump`) → Workflow Client swaps the task's
candidate group behandelaar → teamlead (§8.2).
> Both branches (escalate after 14 days; no-op when completed in time) are covered by the
> `Een beoordeling escaleren` acceptance scenarios and the Workflow Client unit tests; the timer firing
> and reassignment are asserted live by the verify-domain check.