14 Commits
Author SHA1 Message Date
ehoandClaude Opus 5 6cfd70eeeb fix(backend): resolve besluit endpoint's id via Referentie, not local PK
POST /beoordeling/{id}/besluit always 404'd against a real OpenZaak: {id} is the
FE-facing case id from IZaakSource.ListCases, which under OpenZaakZaakSource is the
ZGW zaak's own uuid, not ApplicationStore's primary key. Resolve the case through
ListCases first (same seam the GET sibling already uses), then to the local Aanvraag
via its Referentie — the one identifier stable across both sources.

Adds ApplicationStore.GetByReferentie and a regression test that reproduces the
divergence with a decorating IZaakSource test double instead of a live OpenZaak.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:20:36 +02:00
ehoandClaude Sonnet 5 d996ca2463 feat(behandelportal): WP-66 wire the decision into OpenZaak
Extends IZaakSource with RecordBesluit, mirroring WP-50's CreateZaak write
pattern: OpenZaakZaakSource POSTs a new Statussen entry (highest-volgnummer
statustype, since the harness catalogus has no per-outcome besluittype),
carrying the besluit + toelichting in statustoelichting; LocalZaakSource
no-ops. The beoordeling endpoint calls it after the local decision commits,
flagging a failure via RecordZgwDivergence the same way submit's
create-zaak/document writes do — closing WP-60's "second write pair" gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 09:41:28 +02:00
ehoandClaude Sonnet 5 a09c4ed87b feat(behandelportal): WP-62 medewerker caller identity + authz seam
Splits backend CallerIdentity into the two ADR-0002 §3 actor kinds
(ZorgverlenerCaller/MedewerkerCaller), a stub X-Medewerker/X-Rollen header
path mirroring WP-53's citizen stub, and Authz.CanBeoordelen as the first
medewerker capability — backend-only, no consumer until WP-64. Also fixes
the backlog README's stale WP-61 status (done, but table said todo).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 23:47:43 +02:00
ehoandClaude Sonnet 5 3ff80c124f feat(openzaak): bounded retry + flagged write divergence (WP-60)
Local aanvraag/document writes and their paired ZGW writes aren't
transactional; a ZGW failure after the local write succeeds used to
diverge silently. ZgwHttpClient now retries transport-shaped failures
(not 500, which can follow a partial commit on the non-idempotent
statussen/rollen POSTs), and a ZGW failure that survives retry sets
Aanvraag.ZgwError plus a zgw:divergence audit row instead of failing
or diverging quietly. No outbox/reconcile job: three request-triggered
write paths don't justify a persisted queue that would also need to
carry citizen PII for the JWT audit claims.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 18:11:55 +02:00
ehoandClaude Sonnet 5 3e983bd2cc feat(openzaak): real notification delivery to the BFF webhook (WP-58)
OpenZaak doesn't serve the Notificaties API itself (it's a separate app,
open-notificaties) — standing one up for a real abonnement would triple
this harness for a benefit it doesn't need (exactly one subscriber, this
repo's own BFF). Instead, an opt-in compose overlay adds a celery worker
and points OpenZaak's NotificationsConfig straight at the BFF's webhook
via a zgw_consumers Service; bootstrap-notificaties.sh configures it
idempotently and verify-notificatie.sh proves a real write delivers to
the BFF's audit trail end-to-end.

Verified live: preflight proves the webhook's shared-secret gate both
ways (204/401), a zaak PATCH triggers real celery delivery, and rerunning
both scripts against an already-configured harness stays idempotent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 15:33:16 +02:00
ehoandClaude Sonnet 5 29257048b5 fix(format): prettier drift in 2 docs (pre-existing, caught by format:check)
No content change — markdown list-marker/line-wrap normalization only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 09:24:53 +02:00
ehoandClaude Sonnet 5 5cb3e1a9f0 feat(zgw): docker OpenZaak integration-test harness (WP-54)
Opt-in docker-compose (postgres+redis+OpenZaak, no celery/nginx) +
bootstrap-catalogus.sh seed a real OpenZaak instance; OpenZaakIntegrationTests
(Category=Integration, excluded from default dotnet test/CI) proves the ZGW
seam against it for the first time. That live run caught a real bug:
ZgwHttpClient never sent Content-Crs/Accept-Crs headers, so every write would
412 against a spec-compliant OpenZaak — fixed alongside the harness.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 09:08:35 +02:00
ehoandClaude Sonnet 5 73172510ea feat(zgw): real per-request identity seam + citizen-scoping (WP-53)
CI / frontend (push) Failing after 1m19s
CI / backend (push) Successful in 2m0s
CI / e2e (push) Successful in 3m57s
CI / storybook-a11y (push) Successful in 7m45s
CI / semgrep (push) Successful in 1m6s
CI / api-client-drift (push) Successful in 1m55s
Replaces the hardcoded DocumentStore.DemoOwner and the static ZgwOptions
UserId/UserRepresentation with one per-request CallerIdentity, resolved by a
pluggable IIdentityProvider (StubIdentityProvider reads X-Role/X-Subject
today; a real OIDC/DigiD provider swaps in without touching any consumer).

- Domain/Authorization/{CallerIdentity,IIdentityProvider,StubIdentityProvider}.cs
  + a resolution middleware in Program.cs, right after correlation-id.
- Authz.ResolvePrincipal(ctx) keeps its signature (now reads ctx.Caller().Role),
  so its ~15 call sites needed no changes.
- Every endpoint that passed DocumentStore.DemoOwner to a store now passes
  ctx.Caller().Bsn.
- ZgwTokenProvider gains Mint(CallerIdentity) alongside the original Mint()
  (kept for calls not tied to one citizen); ZgwHttpClient threads an optional
  caller through to pick the right overload.
- IZaakSource gains ListMyCases(caller, now) — the citizen-scoped read
  OpenZaakZaakSource backs with ZGW's rol__...__inpBsn filter. GET /applications
  now routes through it instead of ApplicationStore directly, closing the last
  "reads a static store" gap for a citizen-facing endpoint.

Backend 159/159 tests (+8, incl. an HTTP-level two-identity scoping proof),
npm run ci green, no api-client drift.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 08:27:53 +02:00
ehoandClaude Sonnet 5 bea04549dd feat(zgw): finish WP-52 OpenZaak Notificaties (NRC) webhook slice
CI / frontend (push) Successful in 2m33s
CI / backend (push) Successful in 1m45s
CI / storybook-a11y (push) Successful in 7m47s
CI / e2e (push) Successful in 4m3s
CI / semgrep (push) Successful in 1m7s
CI / api-client-drift (push) Successful in 2m3s
Endpoint/DTO/options landed already in c4dd846; this closes the loop with
NotificatieTests.cs (accept/reject/missing-header, asserting the AuthzAuditStore
row), missing appsettings.json keys (also backfills DrcBaseUrl/
InformatieobjecttypeUrls, stale since WP-51), and the webhook + abonnement
provisioning docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 08:07:50 +02:00
ehoandClaude Sonnet 5 5807937229 feat(zgw): OpenZaak Documenten (DRC) upload + zaak link (WP-51)
Extends the OpenZaak seam with IDocumentSource, sibling of IZaakSource
(WP-49/50): an upload always lands locally first (DocumentStore stays
the record of truth for preview/download/audit) and, when
Zgw:Enabled=true, is also registered as a DRC enkelvoudiginformatie-
object; once a zaak exists (IZaakSource.CreateZaak now also returns
its ZaakUrl), submit links each document to it via zaakinformatie-
object. FE upload/list DTOs are unchanged.

- ZgwOptions gains DrcBaseUrl + a category->informatieobjecttype URL
  map (the document analogue of ZaaktypeUrls).
- LocalDocumentSource is the same DocumentStore.Add/Link calls the
  endpoints used to make inline — zero behaviour change offline.
- OpenZaakDocumentSource POSTs the eio then the zaak link, persisting
  the DRC url (DocumentStore.SetDrcUrl) so linking doesn't re-upload.
- Factored the GET/POST-with-bearer-JWT plumbing shared with
  OpenZaakZaakSource into ZgwHttpClient; shared the stub handler
  between the two source test classes as ZgwStubHandler.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 20:54:31 +02:00
ehoandClaude Sonnet 5 de3bff0d7f feat(zgw): OpenZaak create-zaak, first write slice (WP-50)
Extends the IZaakSource seam (WP-49, read-only) with CreateZaak: submitting
an aanvraag now also registers a Zaak + Status + Rol in OpenZaak when
Zgw:Enabled=true, routed through the existing /applications/{id}/submit
endpoint with the FE response DTO unchanged (ADR-0001/ADR-0005 — the
endpoint never branches on the config flag itself, DI already picked the
implementation).

- ZgwOptions gains a Type→zaaktype-URL map + the two RSINs a Zaak needs.
- LocalZaakSource.CreateZaak is a pure passthrough of what the endpoint
  already computes locally (zero behaviour change for the offline default).
- OpenZaakZaakSource.CreateZaak POSTs the zaak (identificatie = the same
  local reference, so both stay in sync), resolves + POSTs the initial
  status and the initiator rol (BSN) via Catalogi lookups, and maps the
  result back into the submit response.
- Marked ponytail shortcuts: first-statustype/roltype-Catalogi-returns
  (no per-type config) and no compensating transaction on partial failure
  — both fine for a first slice against a demo backend.

Verified: full `npm run ci` green, zero api-client drift, 144/144 backend
tests (142 existing + 2 new stub-handler tests asserting the POST bodies
+ type→zaaktype mapping per the acceptance criteria).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 09:03:13 +02:00
ehoandClaude Opus 4.8 dfd64baba8 docs(backlog): add WP-53 (identity seam + citizen-scoping) and WP-54 (OpenZaak harness)
CI / frontend (push) Successful in 2m20s
CI / backend (push) Successful in 1m51s
CI / e2e (push) Successful in 3m20s
CI / storybook-a11y (push) Failing after 8m35s
CI / semgrep (push) Successful in 1m5s
CI / api-client-drift (push) Successful in 1m52s
The two highest-value OpenZaak roadmap gaps, each written self-contained (a "current
state" handoff section) so a fresh session can execute from the file + repo alone:

- WP-53: replace the stubbed owner/BSN with a real per-request CallerIdentity
  (pluggable stub, not DigiD), threading it into Authz, the ZGW JWT user claims, and
  a citizen-scoped read (rol__…__inpBsn). Production-blocking for a real deployment.
- WP-54: a separate docker-compose OpenZaak + scripted bootstrap + opt-in
  Category=Integration test — makes 50/51/52 developable against a live instance
  instead of only fixtures; kept out of the default gate.

Indexed in the backlog README (rows + phase-9 ordering note) and cited from
openzaak-integration.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 09:05:58 +02:00
ehoandClaude Opus 4.8 a37dfd47a4 ci: cap storybook-a11y resources + document the ACL learnings
CI / frontend (push) Successful in 3m22s
CI / backend (push) Canceled after 0s
CI / e2e (push) Canceled after 0s
CI / semgrep (push) Canceled after 0s
CI / api-client-drift (push) Canceled after 0s
CI / storybook-a11y (push) Canceled after 24s
- test-storybook:ci gets --maxWorkers=2 so the Jest runner stops spawning one
  headless Chromium per core and OOM-ing the Gitea runner host (the root cause).
- storybook-a11y job gains a container resource ceiling (--cpus=2 --memory=4g) as
  a belt-and-suspenders guardrail; noted it needs a docker-mode act_runner.
- openzaak-integration.md: add "Anti-corruption layer — two nested boundaries"
  teaching section (BFF ACL vs upstreams + FE ACL vs BFF, and the principles).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:21:28 +02:00
ehoandClaude Opus 4.8 1c3c195d32 feat(backend): expand stamdata + OpenZaak-ready cases seam (WP-49)
CI / frontend (push) Successful in 2m59s
CI / backend (push) Successful in 1m27s
CI / semgrep (push) Successful in 58s
CI / e2e (push) Successful in 2m30s
CI / api-client-drift (push) Canceled after 1m14s
CI / storybook-a11y (push) Canceled after 29m8s
Stamdata: add beroepen, opleidingen (temporal), and specialismen tables to the
schema-driven catalog (zero UI code). opleidingen.beroep and specialismen.beroep
both reference beroepen.code — the first stamdata->stamdata references, enforced by
two new StamdataRef entries in the CI gate.

OpenZaak/ZGW (WP-49, slice 1 — read-only zaken): introduce IZaakSource as the cases
read seam. Default LocalZaakSource reads the local SQLite store (offline); an
OpenZaakZaakSource (Zgw/ client: HS256 per-call JWT, ZGW->existing-DTO mapper,
paginating HTTP source) is selected behind Zgw:Enabled (default false). The FE never
changes — same ApplicationSummaryDto, no api-client drift. Unit-tested with fixtures
+ a stub HttpMessageHandler; no live OpenZaak needed.

Docs: ADR-0005, reference/openzaak-integration.md, WP-49..52 roadmap, stamdata.md
update, README index rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:01:06 +02:00