The verify stack is shared: earlier verify-projection/verify-domain checks leave
INGEDIEND rows in the register before the e2e submits. A status-only poll
short-circuited on a stale row, then the reference assertion ran against a
not-yet-reloaded page and timed out. Poll (with reload) on this submission's
reference cell instead — stronger, and it directly proves the #78 correlation.
Scope the INGEDIEND/INGESCHREVEN status checks to that reference row too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records the domain->ACL (write) and event-subscriber->ACL (read) boundary for the
single citizen reference, wires Acl__BaseUrl into the subscriber in compose, extends
the e2e happy path to assert confirmation reference == register reference, and adds
the demo note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The BFF's public view exposes id/status/reference (never bsn/naam) and searches by
id or reference; the openbaar register's Referentie column and search now show the
reference the citizen saw on submit. api-client + openapi.json regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On each notification the subscriber reads the zaak's reference (identificatie)
through the ACL — the only code allowed to talk to ZGW (§8.1) — and persists it on
the register_projection row and in the processed_notifications replay log. Storing
it in the log keeps rebuild log-only (ADR-0008): no ACL/ZGW access on rebuild.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenZaakWorker forwards registration.Id to IAclClient.OpenZaakAsync so the zaak's
identificatie equals the reference the citizen sees on the submit confirmation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ACL now writes the domain registrationId as the zaak's identificatie on
POST /zaken, and exposes GET-through POST /zaken/reference to read a zaak's
identificatie back. Only the ACL touches ZGW (§8.1); the reference is the single
value shown on both the self-service confirmation and the openbaar register.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What & why
S-09b (#75, split from #10) — the **approval flow** that completes the walking skeleton. A behandelaar can now approve a submitted registration; the entry flips from `INGEDIEND` to `INGESCHREVEN` in the public register. Flow: `POST /registrations/{id}/approve` (domain) → ACL sets the zaak eindstatus (ZGW `/statussen`) → OpenZaak → NRC → event-subscriber → projection → openbaar.
## Changes (bottom-up, each red→green TDD)
- **Domain** — `RegistrationStatus.Ingeschreven` + `Registration.Approve()` (guards: opened zaak, only from INGEDIEND); `ApproveRegistration` use case (idempotent) + temp `POST /registrations/{id}/approve` endpoint; `IAclClient.ApproveZaakAsync`.
- **ACL** — resolves the zaaktype's **eindstatus** from the catalogus (`isEindstatus` / highest volgnummer) and POSTs a ZGW status; exposed as `POST /statussen`. Unit + real-OpenZaak integration test.
- **Event-subscriber** — binds NRC `hoofdObject`, projects a `status`/`create` as `INGESCHREVEN` keyed on the zaak (updates the existing row), **without reading OpenZaak** (§8.1). Retains the ZGW `resource` in the log (new column + EF migration) so a rebuild reproduces the status.
- **e2e** — extended: submit → public INGEDIEND → approve → public INGESCHREVEN.
- **Docs** — ADR-0011 (the two non-obvious decisions + the walking-skeleton assumption) + demo note.
## Key decisions (see ADR-0011)
- **ACL discovers the eindstatus** (chosen over injecting a statustype URL): no new config/seed plumbing, domain stays ZGW-ignorant.
- **Any post-creation status-set ⇒ INGESCHREVEN**: in the walking skeleton the only status ever set after creation is the approval, and the subscriber may not read ZGW — documented to tighten when more transitions arrive (S-12+).
## Verification
- All .NET unit suites green locally (domain 47, acl 11, event-subscriber 14, bff 16, acceptance 7); Release build + `dotnet format` clean.
- No new compose config (the eindstatus-discovery approach avoided it).
- The real-OpenZaak integration test (ACL status-set) and the full submit→approve→visible e2e run in CI `verify-stack` (live NRC→projection + selectielijst egress, not reproducible locally).
closes#75
Reviewed-on: #77
The walking-skeleton e2e timed out waiting for the Keycloak login form
(`#username`). Root cause: in the compose network the portal is served over
plain HTTP on a non-localhost origin (http://self-service), which is not a
secure context, so Web Crypto (`crypto.subtle`) is undefined. angular-auth-
oidc-client needs SubtleCrypto to build the PKCE code challenge, so
`authorize()` threw ("Cannot read properties of undefined (reading 'digest')")
and the login redirect never fired.
Production serves the portal over HTTPS, where this works. Instead of
terminating TLS in the throwaway e2e stack, tell Chromium to treat the origin
as secure via --unsafely-treat-insecure-origin-as-secure. The flag is only
honoured by the full Chromium build (new headless), not Playwright's default
headless-shell, so pin channel: 'chromium'.
Verified against a minimal in-network stack (keycloak + self-service): login
redirect now reaches the Keycloak form, and the full login → token exchange →
authenticated portal renders with no console errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tests/e2e Playwright spec drives DigiD login (jan-burger/test123) → submit →
confirmation against the compose-served portal. run-e2e-check.sh runs it inside the
compose network (node container, browser installed at runtime) so the token issuer
(keycloak:8080) matches the BFF authority (ADR-0010). Wired as verify-e2e (Makefile +
verify chain + a verify-stack CI step).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use-case-level BDD (Reqnroll) driving the real BFF over HTTP with fake downstreams
and locally-minted tokens: a valid DigiD token is accepted and the bsn forwarded
to the domain; a tokenless submit is 401; the openbaar register is anonymous and
never exposes the bsn (ADR-0010). Real Keycloak validation is the verify-bff check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use-case-level BDD (Reqnroll) for S-05: a zorgprofessional submits a registration;
the Domain Service starts the registratie process and the OpenZaakAanmaken external
task opens a zaak via the ACL, recorded on the aggregate (ADR-0009). Driven against
in-memory Workflow Client and ACL stand-ins; real Flowable+ACL+OpenZaak delivery is
the live-stack verify-domain check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failing unit + acceptance tests for the Event Subscriber's NotificationProjector:
a zaken/zaak/create notification yields one INGEDIEND projection row, duplicate
deliveries collapse to one row, non-zaak/non-create notifications are ignored, and
a rebuild repopulates the projection from the durable notification log (PRD §8.4).
The projector is a no-op stub so the tests compile and fail on the assertions; the
implementation follows in the green commit. The notification log doubles as the
idempotency guard and rebuild source so a rebuild needs no OpenZaak access (§8.1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>