feat(workflow): beoordeling escalation to teamlead after 14 days (S-14, closes #15) #99

Merged
not merged 10 commits from feat/15-beoordeling-escalation into main 2026-07-17 09:45:37 +00:00
Contributor

What & why

S-14: a beoordeling a behandelaar does not pick up within 14 days escalates to the teamlead.

A non-interrupting P14D boundary timer on the Beoordelen user task fires an external-worker task
(BeoordelingEscaleren); the domain's escalation worker reassigns the still-open task's candidate group
from behandelaar to teamlead. The task keeps its identity — only who may claim it changes. The
escalation-via-external-worker decision is recorded in ADR-0015 (proposal #98); it upholds §8.2
(the Workflow Client stays the only code that talks to Flowable) and keeps Flowable a stock image.

Closes #15

Definition of Done

  • Linked Gitea issue (above).
  • Failing test committed before the implementation.
  • Implementation makes the test pass; refactor commit if structure improved.
  • Conventional Commits referencing the issue (refs #NN).
  • CI green — all Gitea Actions jobs.
  • docker compose up from a fresh clone reaches green health checks within 3 minutes (no new services; escalation is additive to the domain worker).
  • Docs updated (ADR-0015, demo note).
  • ADR added (docs/architecture/adr-0015-beoordeling-escalation.md).
  • Demo note in docs/demo-script.md.

How it was built (TDD)

  • Workflow Client (IBeoordelingEscalatieClient): acquire BeoordelingEscaleren jobs → find the open Beoordelen task in the instance → add teamlead/remove behandelaar candidate group → complete the job. Red → green.
  • Escalation drain loop (BeoordelingEscalatieProcessor) + hosted BeoordelingEscalatiePump, mirroring the OpenZaak worker. Red → green.
  • BPMN: non-interrupting P14D boundary timer on Beoordelen → external task → escalation end.
  • Both branches (escalate after timeout; no-op when completed in time) covered by the Een beoordeling escaleren acceptance scenarios + Workflow Client unit tests.
  • Live integration: verify-domain fires the timer early via Flowable's management API and asserts the reassignment to teamlead.

Notes for reviewers

  • Interface segregation: escalation is on IBeoordelingEscalatieClient, separate from the OpenZaak worker's IExternalWorkerClient.
  • Reassignment is two REST hops (add teamlead, remove behandelaar); idempotent on redelivery — see ADR-0015 consequences.
  • Local checks green: domain unit tests (104), acceptance (13), dotnet format --verify-no-changes, Release build (0 errors), domain mutation 96.69% (break 90). The run-domain-check.sh escalation path is CI-verified on verify-stack (local full-stack run is constrained here).
  • BeoordelingEscalatiePump excluded from mutation, mirroring the existing OpenZaakJobPump exclusion.
## What & why S-14: a beoordeling a behandelaar does not pick up within **14 days** escalates to the **teamlead**. A non-interrupting `P14D` boundary timer on the `Beoordelen` user task fires an external-worker task (`BeoordelingEscaleren`); the domain's escalation worker reassigns the still-open task's candidate group from `behandelaar` to `teamlead`. The task keeps its identity — only who may claim it changes. The escalation-via-external-worker decision is recorded in **ADR-0015** (proposal #98); it upholds §8.2 (the Workflow Client stays the only code that talks to Flowable) and keeps Flowable a stock image. Closes #15 ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation. - [x] Implementation makes the test pass; refactor commit if structure improved. - [x] Conventional Commits referencing the issue (`refs #NN`). - [x] CI green — all Gitea Actions jobs. - [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes (no new services; escalation is additive to the domain worker). - [x] Docs updated (ADR-0015, demo note). - [x] ADR added (`docs/architecture/adr-0015-beoordeling-escalation.md`). - [x] Demo note in `docs/demo-script.md`. ## How it was built (TDD) - **Workflow Client** (`IBeoordelingEscalatieClient`): acquire `BeoordelingEscaleren` jobs → find the open `Beoordelen` task in the instance → add `teamlead`/remove `behandelaar` candidate group → complete the job. Red → green. - **Escalation drain loop** (`BeoordelingEscalatieProcessor`) + hosted `BeoordelingEscalatiePump`, mirroring the OpenZaak worker. Red → green. - **BPMN**: non-interrupting `P14D` boundary timer on `Beoordelen` → external task → escalation end. - **Both branches** (escalate after timeout; no-op when completed in time) covered by the `Een beoordeling escaleren` acceptance scenarios + Workflow Client unit tests. - **Live integration**: `verify-domain` fires the timer early via Flowable's management API and asserts the reassignment to teamlead. ## Notes for reviewers - Interface segregation: escalation is on `IBeoordelingEscalatieClient`, separate from the OpenZaak worker's `IExternalWorkerClient`. - Reassignment is two REST hops (add teamlead, remove behandelaar); idempotent on redelivery — see ADR-0015 consequences. - Local checks green: domain unit tests (104), acceptance (13), `dotnet format --verify-no-changes`, Release build (0 errors), **domain mutation 96.69%** (break 90). The `run-domain-check.sh` escalation path is CI-verified on verify-stack (local full-stack run is constrained here). - `BeoordelingEscalatiePump` excluded from mutation, mirroring the existing `OpenZaakJobPump` exclusion.
not added 9 commits 2026-07-17 07:17:12 +00:00
Failing Workflow Client tests for the 14-day escalation capability (ADR
proposal #98): acquire BeoordelingEscaleren external-worker jobs, reassign the
still-open Beoordelen task's candidate group behandelaar -> teamlead, and
complete the escalation job. Methods are stubbed to throw so the suite compiles
and fails on the new behaviour only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement the BeoordelingEscaleren escalation capability on the Flowable
Workflow Client: acquire escalation jobs, find the still-open Beoordelen task in
the instance and swap its candidate group behandelaar -> teamlead via task
identity links, and complete the job. Segregated onto IBeoordelingEscalatieClient
so the OpenZaak worker is unaffected (ADR-0015).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failing tests for BeoordelingEscalatieProcessor: acquire the parked
BeoordelingEscaleren jobs, reassign each instance to teamlead, complete the job,
and leave a failed reassignment un-completed for redelivery. PumpOnceAsync is
stubbed to throw so the suite compiles and fails on the new behaviour only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement BeoordelingEscalatieProcessor.PumpOnceAsync over the escalation client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a non-interrupting P14D boundary timer on the Beoordelen user task that fires
an external-worker task (BeoordelingEscaleren) to the escalation end; wire the
hosted BeoordelingEscalatiePump and register the escalation client/processor in DI.
On timeout the still-open beoordeling is reassigned to teamlead (ADR-0015).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gherkin scenarios for the two branches: a beoordeling left unclaimed for 14 days
escalates to the teamlead, and one completed before the timer fires does not.
Driven through the escalation worker against an in-memory Flowable stand-in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the live verify-domain check: a third registration parks at Beoordelen,
its boundary timer is fired early via Flowable's management API, and the check
polls until the domain escalation worker reassigns the still-open task's
candidate group from behandelaar to teamlead (ADR-0015). Runs on the CI
verify-stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
chore(test): exclude BeoordelingEscalatiePump from mutation (S-14, refs #15)
Some checks failed
CI / lint (pull_request) Successful in 4m21s
CI / build (pull_request) Successful in 1m3s
CI / unit (pull_request) Successful in 1m8s
CI / frontend (pull_request) Successful in 2m37s
CI / mutation (pull_request) Successful in 5m14s
CI / verify-stack (pull_request) Failing after 6m36s
b17c2c51b1
The hosted escalation pump is a thin shell with logic in the unit-tested
processor, mirroring the existing OpenZaakJobPump exclusion (§5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not added the type:slicearea:workflow labels 2026-07-17 07:17:27 +00:00
not added this to the Iteration 2 — Flow Completeness milestone 2026-07-17 07:17:28 +00:00
not added 1 commit 2026-07-17 07:40:44 +00:00
fix(infra): don't require the moved timer job to linger in verify-domain (S-14, refs #15)
All checks were successful
CI / lint (pull_request) Successful in 1m16s
CI / build (pull_request) Successful in 1m0s
CI / unit (pull_request) Successful in 1m9s
CI / frontend (pull_request) Successful in 2m30s
CI / mutation (pull_request) Successful in 5m22s
CI / verify-stack (pull_request) Successful in 7m15s
eeb2a9cb40
Flowable's async executor picks up the moved timer job and fires the boundary
event before the check can GET /management/jobs, so requiring that async job to
still exist raced and failed. The move alone fires the timer; the explicit
execute is now a best-effort nudge that tolerates the job already being gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not merged commit 7bcbc726ce into main 2026-07-17 09:45:37 +00:00
Sign in to join this conversation.