Files
register-referentie/docs/architecture/adr-0017-document-wait-timeout-cancellation.md
T
notandClaude Opus 4.8 d943b54ce8
CI / lint (pull_request) Successful in 1m18s
CI / build (pull_request) Successful in 59s
CI / unit (pull_request) Successful in 1m10s
CI / frontend (pull_request) Successful in 2m33s
CI / mutation (pull_request) Successful in 10m22s
CI / verify-stack (pull_request) Failing after 11m24s
docs(workflow): ADR-0017 + demo note for the document-wait timeout (refs #102)
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>
2026-07-20 09:59:36 +02:00

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 RegistratieVerlopen external-worker task (topic mirrors OpenZaakAanmaken/BeoordelingEscaleren) → endVerlopen.
  • The domain stays authoritative. The RegistratieVerlopen job carries the registrationId; the RegistratieVerlopenProcessor drains it and the ExpireRegistrationWorker loads the aggregate and calls Registration.Expire(), moving it to the new terminal status Verlopen. 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 already Verlopen completes without persisting again; an unknown registration throws so the job is redelivered.
  • Documents-in-time transition. IWorkflowClient.CompleteDocumentWaitAsync(processInstanceId) completes the WachtOpDocumenten task (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 WachtOpDocumenten before 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 Verlopen but 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 same RequireOpenForDecision guard as withdrawal/decision, so only an INGEDIEND/IN_BEHANDELING registration can lapse and the terminal states stay mutually exclusive — no new guard logic.