Records the interrupting P30D WachtOpDocumenten timer, the RegistratieVerlopen worker, and the new terminal Verlopen status; notes the S-10a/S-10b boundary (ZGW zaak-close deferred). Demo covers both branches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.3 KiB
ADR-0017: A document-wait task with a 30-day interrupting timer cancels the registration
- Status: Accepted
- Date: 2026-07-20
- Deciders: Respellion engineering
- Relates to: S-10a (#102); proposal #104; split from S-10 (#11). Builds on ADR-0009 (external-task worker / Workflow Client), ADR-0014 (withdrawal cancels the process), ADR-0015 (beoordeling escalation — the boundary-timer + external-worker pattern), ADR-0016 (diploma-eligibility DMN).
Context
Flow 2 (PRD §5) requires the citizen to supply documents (their diploma) after submitting. The registratie process must park waiting for those documents and, if they do not arrive within 30 days, cancel the case. S-10 was split (§13): S-10a is this workflow/timeout spine (backend only); S-10b wires the actual upload (portal → BFF → domain → ACL → Documenten API) that completes the wait. This ADR records the spine: where the wait sits, how the timeout cancels, and how the domain aggregate stays in sync.
Decision
A WachtOpDocumenten user task is inserted immediately after OpenZaakAanmaken, carrying an
cancelActivity="true" (interrupting) P30D boundary timer. "Documents received" completes the task
and the process continues into the diploma-eligibility routing; on timeout the timer cancels the task,
runs a RegistratieVerlopen external-worker task, and ends the process at endVerlopen. A domain
worker expires the correlated aggregate to a new terminal status Verlopen.
- Where the wait sits. Right after the zaak is opened, before the diploma-eligibility DMN: the zaak exists, then the process waits for documents; on receipt it continues to the DMN routing → Beoordelen (ADR-0016). The wait gates the whole assessment, so it precedes the routing rather than sitting between the gateway and Beoordelen.
- Interrupting timer, mirroring the existing constructs. Unlike the S-14 escalation timer
(non-interrupting — the Beoordelen task stays open), this timer is interrupting: when it fires the
wait token is consumed and the case is cancelled, like the S-11 withdrawal boundary (ADR-0014). The
timeout branch runs a
RegistratieVerlopenexternal-worker task (topic mirrorsOpenZaakAanmaken/BeoordelingEscaleren) →endVerlopen. - The domain stays authoritative. The
RegistratieVerlopenjob carries theregistrationId; theRegistratieVerlopenProcessordrains it and theExpireRegistrationWorkerloads the aggregate and callsRegistration.Expire(), moving it to the new terminal statusVerlopen. This keeps the aggregate — which the projection/openbaar view reads — the source of truth, exactly as escalation and withdrawal do. Idempotent per §8.6: a redelivered job whose aggregate is alreadyVerlopencompletes without persisting again; an unknown registration throws so the job is redelivered. - Documents-in-time transition.
IWorkflowClient.CompleteDocumentWaitAsync(processInstanceId)completes theWachtOpDocumententask (the Workflow Client remains the only code that talks to Flowable, §8.2). It is best-effort — a no-op if the instance already left the wait (continued, or timed out). The trigger that calls it (the portal upload) is wired in S-10b; S-10a builds and tests the completion path with the trigger stubbed (the live check completes the task directly to prove the in-time branch, and the domain acceptance drives the worker against an in-memory stand-in).
Consequences
Positive
- The wait/timeout is a first-class workflow construct that reuses the boundary-timer + external-worker pattern already proven by S-14, so the domain change is small and additive: one terminal status, one worker trio (worker + processor + pump), one Workflow Client method.
- §8 stays clean: the Workflow Client is still the only Flowable caller, and no new ZGW boundary is introduced in S-10a.
- The timeout is verified live (verify-domain fires the P30D timer via the management-API "move" idiom
and asserts the domain reaches
Verlopen), consistent with ADR-0009/0014/0015.
Negative / costs
- Every registration now parks at
WachtOpDocumentenbefore Beoordelen, so the other live-check blocks (S-11/S-12b/S-13/S-14) must complete that task first — a small, explicit step standing in for the S-10b upload until it lands. - On expiry S-10a cancels the process and marks the aggregate
Verlopenbut does not set the ZGW zaak to a cancellation status — that needs a new ACL method + statustype seeding, which overlaps S-10b's ACL/infra work. Deferred to S-10b (or a follow-up); noted here as the S-10a/S-10b boundary.
Alternatives considered
- Pure-BPMN cancellation (timer → end event, no worker). Rejected: the domain aggregate would then be out of sync with the cancelled process, and the openbaar/projection view reads the aggregate's status — the case would still look open.
- Wait task between the gateway and Beoordelen. Rejected: documents gate the whole assessment (including the CBGV-advies routing), so the wait belongs before the DMN, not after it.
- A dedicated timeout status per branch vs. reusing an open-state guard.
Expire()reuses the sameRequireOpenForDecisionguard as withdrawal/decision, so only anINGEDIEND/IN_BEHANDELINGregistration can lapse and the terminal states stay mutually exclusive — no new guard logic.