docs(acl): ADR-0019 + demo/backlog for zaak cancellation on timeout (S-10c) (refs #106)
CI / lint (pull_request) Successful in 1m22s
CI / build (pull_request) Successful in 1m2s
CI / unit (pull_request) Successful in 1m14s
CI / frontend (pull_request) Successful in 2m46s
CI / mutation (pull_request) Failing after 2m30s
CI / verify-stack (pull_request) Has been cancelled
CI / lint (pull_request) Successful in 1m22s
CI / build (pull_request) Successful in 1m2s
CI / unit (pull_request) Successful in 1m14s
CI / frontend (pull_request) Successful in 2m46s
CI / mutation (pull_request) Failing after 2m30s
CI / verify-stack (pull_request) Has been cancelled
Records the ZGW cancellation-modelling decision (distinct non-terminal Geannuleerd status + Vervallen resultaat, resolved by name) and documents the end-to-end timeout-cancellation path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# ADR-0019: A timed-out zaak is cancelled with a distinct status + resultaat, resolved by name
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-07-21
|
||||
- **Deciders:** Respellion engineering
|
||||
- **Relates to:** S-10c (#106). Completes the S-10a/S-10b boundary noted in ADR-0017 (§Consequences) and
|
||||
reuses the ACL close-zaak machinery from S-09b (approval) and the Documenten work in ADR-0018.
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0017 (S-10a) cancels the *process* and marks the domain aggregate `Verlopen` when the 30-day
|
||||
document term lapses, but explicitly deferred setting the ZGW **zaak** to a cancellation status. Left
|
||||
open, a timed-out zaak stays open in OpenZaak while the register shows the registration as lapsed — the
|
||||
two diverge. S-10c closes that gap: on expiry the domain must also cancel the zaak through the ACL
|
||||
(§8.1, the only code that talks to ZGW).
|
||||
|
||||
The non-obvious part is *how to represent "cancelled" in ZGW* alongside the existing "approved" close.
|
||||
The approval path (S-09b) sets the zaak's **eindstatus** (the terminal statustype) plus a resultaat. In
|
||||
ZGW a zaaktype has exactly one eindstatus — the highest-`volgnummer` statustype — and setting it is what
|
||||
closes the zaak (`einddatum`). A second *terminal* status would collide with that single-eindstatus rule.
|
||||
|
||||
## Decision
|
||||
|
||||
**Model cancellation as a distinct, non-terminal `Geannuleerd` statustype plus a distinct `Vervallen`
|
||||
resultaat, and resolve both the approval and cancellation statustype/resultaat by their omschrijving
|
||||
(name) rather than by position or the eindstatus flag alone.**
|
||||
|
||||
- **Seed.** `Geannuleerd` is seeded at `volgnummer` 2 — between `Ontvangen` (1) and the `Afgehandeld`
|
||||
eindstatus (3) — so it is a *non-terminal* status and never displaces the eindstatus the approval path
|
||||
resolves. A second resultaattype `Vervallen` (archiefnominatie `vernietigen`) is seeded beside the
|
||||
approval `Geregistreerd` (`blijvend_bewaren`); both draw their `selectielijstklasse` from the
|
||||
zaaktype's single `selectielijstProcestype` so they validate on publish.
|
||||
- **The ACL owns the mapping.** `OpenZaakGateway.SetZaakToCancellationStatusAsync` resolves `Geannuleerd`
|
||||
+ `Vervallen` by omschrijving and POSTs the resultaat then the status (OpenZaak requires a resultaat
|
||||
before a closing/terminal status), mirroring `SetZaakToEindstatusAsync`. Exposed as
|
||||
`AclService.CancelZaakAsync` behind the ACL endpoint `POST /annuleringen`. The omschrijvingen live as
|
||||
constants in the gateway — the ACL, not the domain, knows which ZGW status means what (§8.1).
|
||||
- **Approval now resolves its resultaat by name too.** With two resultaattypen present, taking the first
|
||||
is ambiguous (the Zaken API does not guarantee order), so the approval path resolves `Geregistreerd`
|
||||
by omschrijving. Its statustype resolution is unchanged (still the eindstatus).
|
||||
- **Domain wiring.** The `ExpireRegistrationWorker` calls `IAclClient.CancelZaakAsync(zaakUrl)` **before**
|
||||
advancing the aggregate to `Verlopen` (ACL-first, mirroring approval): if the ACL call fails the job is
|
||||
redelivered (§8.6) rather than leaving the aggregate `Verlopen` with an open zaak. The existing
|
||||
open-state guard stops a redelivered job from cancelling twice (a second resultaat would be a 400); a
|
||||
registration that lapsed before its zaak was opened has nothing to cancel.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive**
|
||||
|
||||
- The domain aggregate and the ZGW zaak no longer diverge on timeout — both reflect the cancellation.
|
||||
- Reuses the approval close machinery (resultaat-then-status, ACL endpoint shape, ACL-first ordering), so
|
||||
the change is additive and §8 stays clean (only the ACL talks to ZGW).
|
||||
- Verified at two levels: an ACL↔OpenZaak integration test asserts the live zaak reaches `Geannuleerd`
|
||||
with a resultaat, and the domain verify script fires the real P30D timer and confirms the zaak is
|
||||
cancelled end-to-end.
|
||||
|
||||
**Negative / costs**
|
||||
|
||||
- `Geannuleerd` is non-terminal, so the cancelled zaak's `einddatum` is not set — it carries a
|
||||
cancellation status + resultaat but is not formally "closed" in ZGW. Accepted: the register reads the
|
||||
domain aggregate's status, and a single eindstatus per zaaktype is a ZGW constraint we chose not to
|
||||
fight. Formally closing a cancelled zaak (a second eindstatus, or reusing `Afgehandeld` with a
|
||||
`Vervallen` resultaat) is a possible follow-up.
|
||||
- The ACL couples to the seeded omschrijvingen (`Geregistreerd`/`Geannuleerd`/`Vervallen`) by string
|
||||
constants. This mirrors the existing implicit coupling to the catalogus (zaaktype URL, eindstatus) and
|
||||
is documented in the gateway.
|
||||
- Renumbering `Afgehandeld` from `volgnummer` 2 to 3 means a *stale* local catalogus must have its
|
||||
OpenZaak volumes reset for the change to take effect; CI reseeds a fresh catalogus each run.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Shared eindstatus, distinct resultaat only** (reuse `Afgehandeld`, distinguish approval vs
|
||||
cancellation purely by the resultaat). ZGW-idiomatic and would set `einddatum` on cancellation too, but
|
||||
the register would show no visibly distinct cancellation *status*. Rejected in favour of the issue's
|
||||
explicit "distinct statustype + resultaattype" outcome, which makes the cancellation legible in ZGW.
|
||||
- **A second terminal (eindstatus) `Geannuleerd`.** Rejected: ZGW allows only one eindstatus per
|
||||
zaaktype (highest volgnummer); a second terminal status would either not close the zaak or collide with
|
||||
the approval eindstatus resolution.
|
||||
- **Passing the target omschrijvingen from the domain.** Rejected: which ZGW status means "cancelled" is
|
||||
ZGW vocabulary the ACL owns (§8.1); the domain says only "cancel this zaak".
|
||||
Reference in New Issue
Block a user