# 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".