docs(backlog): add phase 10 (OpenZaak hardening) and phase 11 (behandelportal)
WP-55..60 harden the OpenZaak integration for production (secrets/TLS, idempotent provisioning, least-privilege scopes, real notifications, confidentialiteit config, write-divergence resilience). WP-61..66 stand up a staff-facing behandelportal per ADR-0002, wired to the same backend via BFF-lite decision DTOs. Both phases are independent tracks; WP-60's Decisions block is deliberately left open for a planner-agent kickoff. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,18 @@ for its existing violations, so every WP ends green.
|
|||||||
| [WP-52](WP-52-openzaak-notificaties.md) | OpenZaak Notificaties (NRC) live status via webhook | 9 · OpenZaak/ZGW | done |
|
| [WP-52](WP-52-openzaak-notificaties.md) | OpenZaak Notificaties (NRC) live status via webhook | 9 · OpenZaak/ZGW | done |
|
||||||
| [WP-53](WP-53-inbound-identity-and-citizen-scoping.md) | Inbound identity seam + citizen-scoping (per-request BSN, ZGW audit claims) | 9 · OpenZaak/ZGW | done |
|
| [WP-53](WP-53-inbound-identity-and-citizen-scoping.md) | Inbound identity seam + citizen-scoping (per-request BSN, ZGW audit claims) | 9 · OpenZaak/ZGW | done |
|
||||||
| [WP-54](WP-54-openzaak-integration-harness.md) | Docker OpenZaak integration-test harness (opt-in, live round-trip) | 9 · OpenZaak/ZGW | done |
|
| [WP-54](WP-54-openzaak-integration-harness.md) | Docker OpenZaak integration-test harness (opt-in, live round-trip) | 9 · OpenZaak/ZGW | done |
|
||||||
|
| [WP-55](WP-55-openzaak-secrets-tls.md) | Real secrets + TLS for the OpenZaak harness | 10 · OpenZaak hardening | todo |
|
||||||
|
| [WP-56](WP-56-openzaak-catalogus-provisioning.md) | Idempotent catalogus provisioning | 10 · OpenZaak hardening | todo |
|
||||||
|
| [WP-57](WP-57-openzaak-least-privilege-scopes.md) | Least-privilege client scopes | 10 · OpenZaak hardening | todo |
|
||||||
|
| [WP-58](WP-58-openzaak-notifications.md) | Real notifications (celery + scripted abonnement) | 10 · OpenZaak hardening | todo |
|
||||||
|
| [WP-59](WP-59-document-confidentialiteit-config.md) | Per-document-type confidentialiteit config | 10 · OpenZaak hardening | todo |
|
||||||
|
| [WP-60](WP-60-write-divergence-resilience.md) | Write-divergence resilience (local + ZGW writes) | 10 · OpenZaak hardening | todo |
|
||||||
|
| [WP-61](WP-61-behandelportal-bootstrap.md) | Bootstrap the behandelportal app | 11 · Behandelportal | todo |
|
||||||
|
| [WP-62](WP-62-medewerker-identity-authz.md) | Backend: medewerker caller identity + authz seam | 11 · Behandelportal | todo |
|
||||||
|
| [WP-63](WP-63-aanvraag-status-lifecycle.md) | Backend: aanvraag status lifecycle as a published DTO | 11 · Behandelportal | todo |
|
||||||
|
| [WP-64](WP-64-behandelportal-werkvoorraad.md) | Behandelportal: werkvoorraad (queue) screen | 11 · Behandelportal | todo |
|
||||||
|
| [WP-65](WP-65-behandelportal-beoordeling.md) | Behandelportal: zaak detail + beoordeling (decision) screen | 11 · Behandelportal | todo |
|
||||||
|
| [WP-66](WP-66-behandelportal-openzaak-write.md) | Wire the decision into OpenZaak | 11 · Behandelportal | todo |
|
||||||
|
|
||||||
Sequencing dependencies (stated in the WPs too): 01 before 10–15 (axe covers story churn);
|
Sequencing dependencies (stated in the WPs too): 01 before 10–15 (axe covers story churn);
|
||||||
03/04 before 05–09 (boundaries stop new violations during refactors); 06 before 07 (typed
|
03/04 before 05–09 (boundaries stop new violations during refactors); 06 before 07 (typed
|
||||||
@@ -137,6 +149,17 @@ deployment of 49–52) and **54** (a docker OpenZaak harness + opt-in integratio
|
|||||||
CRUD arc and can land any time; 54 depends on 49 (something to read) and unlocks realistic
|
CRUD arc and can land any time; 54 depends on 49 (something to read) and unlocks realistic
|
||||||
testing for the rest. Both are self-contained (each WP file carries its own current-state
|
testing for the rest. Both are self-contained (each WP file carries its own current-state
|
||||||
handoff) and sized for a fresh Sonnet session.
|
handoff) and sized for a fresh Sonnet session.
|
||||||
|
Phase 10 (OpenZaak production hardening, WP-55..60) and Phase 11 (Behandelportal,
|
||||||
|
WP-61..66) are two independent tracks that can be worked concurrently — neither blocks
|
||||||
|
the other. Within phase 10: 55/59/60 are fully independent; 57 and 58 both build on 56's
|
||||||
|
provisioning mechanism, otherwise independent of each other. Within phase 11: 61
|
||||||
|
(bootstrap), 62 (backend medewerker identity), and 63 (backend status DTO) are
|
||||||
|
independent of each other and can land in any order; 64 needs all three (61 for the app
|
||||||
|
to exist, 62 for identity, 63 for the status it reads); 65 needs 64; 66 needs 65 and
|
||||||
|
benefits from — but doesn't strictly require — phase 10's WP-60 landing first (WP-66 is
|
||||||
|
a second, currently-unprotected write pair otherwise). WP-60 is the one slice in phase 10
|
||||||
|
sized for a `planner`-agent kickoff rather than direct implementation — its Decisions
|
||||||
|
block is deliberately left open (outbox vs. retry+reconcile).
|
||||||
|
|
||||||
## WP template
|
## WP template
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# WP-55 — Real secrets + TLS for the OpenZaak harness
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 10 — OpenZaak production hardening
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
`backend/openzaak/docker-compose.openzaak.yml` is explicitly a throwaway dev/test harness:
|
||||||
|
`SECRET_KEY: wp-54-local-harness-not-for-prod`, `POSTGRES_HOST_AUTH_METHOD=trust` (no DB
|
||||||
|
password), `IS_HTTPS: 'no'`, `DISABLE_2FA: 'true'`. Before anything else in this phase can
|
||||||
|
be called "production," the instance needs real secrets, real DB auth, and TLS. The BFF
|
||||||
|
side is already fine — `ZgwOptions.cs` binds from `IConfiguration`, so this is a deploy-config
|
||||||
|
change, not application code.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- [openzaak-integration.md](../reference/openzaak-integration.md)
|
||||||
|
- `backend/openzaak/README.md`
|
||||||
|
- [ADR-0005 — OpenZaak behind the BFF](../reference/architecture/0005-openzaak-behind-bff.md)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Secrets come from the deployment environment (env vars / secrets manager), never
|
||||||
|
checked into compose or appsettings.
|
||||||
|
- TLS termination happens at a reverse proxy/ingress in front of OpenZaak — OpenZaak
|
||||||
|
itself doesn't need built-in cert handling.
|
||||||
|
- The existing dev harness stays as-is for local iteration (WP-54's trimmed rig is
|
||||||
|
intentional and still valuable); this WP adds a production compose/override or an
|
||||||
|
env-driven parameterization of the same file, not a replacement of the dev rig.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `backend/openzaak/docker-compose.openzaak.yml` (or a new `docker-compose.openzaak.prod.yml` override)
|
||||||
|
- `backend/openzaak/README.md`
|
||||||
|
- `backend/src/BigRegister.Api/appsettings*.json` / `Zgw/ZgwOptions.cs` (confirm only, likely no change)
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Parameterize `SECRET_KEY`, DB user/password, and the ZGW JWT secret via env vars;
|
||||||
|
remove hardcoded values from the committed file.
|
||||||
|
2. Switch `POSTGRES_HOST_AUTH_METHOD` from `trust` to password auth, password from env.
|
||||||
|
3. Set `IS_HTTPS: 'yes'`; document the required reverse-proxy/ingress TLS termination.
|
||||||
|
4. Update `backend/openzaak/README.md` with the required env vars and the TLS note.
|
||||||
|
5. Confirm the BFF's JWT secret already comes from config — no code change expected.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] No secret value is hardcoded in any committed compose/config file.
|
||||||
|
- [ ] The production compose fails fast (or docs state clearly) when secrets aren't
|
||||||
|
supplied — no silent fallback to a real-looking default.
|
||||||
|
- [ ] README documents exactly which env vars must be set and how TLS is terminated.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`docker compose -f backend/openzaak/docker-compose.openzaak.yml config` with required env
|
||||||
|
vars set; `cd backend && dotnet test` (WP-54 harness tests unaffected); manual: the local
|
||||||
|
dev harness still works with its dev-only values documented as dev-only.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Catalogus provisioning (WP-56), client scope narrowing (WP-57), notifications (WP-58).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
If TLS/secrets docs lag an actual deploy, someone could ship with dev defaults — mitigate
|
||||||
|
by making the prod compose fail without required env vars rather than silently defaulting.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# WP-56 — Idempotent catalogus provisioning
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 10 — OpenZaak production hardening
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
`backend/openzaak/bootstrap-catalogus.sh` seeds catalogus/zaaktype/statustype/roltype/zaak
|
||||||
|
via hand-rolled curl+JWT and is explicitly **not idempotent** (fails on `domein`+`rsin`
|
||||||
|
uniqueness on rerun) — fine for a one-shot WP-54 harness, wrong for an environment that
|
||||||
|
needs to be rebuildable. OpenZaak already ships a documented, scripted alternative — the
|
||||||
|
`setup_configuration` mechanism (already used in the harness for the JWTSecret/Applicatie,
|
||||||
|
see `setup_configuration/data.yaml`) — this WP extends that same mechanism to the catalogus
|
||||||
|
content too.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- `backend/openzaak/bootstrap-catalogus.sh`
|
||||||
|
- `backend/openzaak/setup_configuration/data.yaml`
|
||||||
|
- `backend/openzaak/docker-compose.openzaak.yml` (`web-init` service)
|
||||||
|
- OpenZaak's own `setup_configuration` / `openzaak_config_cli` docs (upstream)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Move catalogus/zaaktype/statustype/roltype provisioning into `setup_configuration`,
|
||||||
|
run by the existing `web-init` one-shot service, instead of the separate curl script.
|
||||||
|
- Keep `bootstrap-catalogus.sh` only for whatever content `setup_configuration` genuinely
|
||||||
|
can't express (e.g. a demo zaak instance) — confirm what's left at kickoff.
|
||||||
|
- Provisioning must be safe to run against an already-provisioned instance — either
|
||||||
|
genuinely idempotent, or the compose is structured to only run it once per fresh
|
||||||
|
volume (document which, don't leave it ambiguous).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `backend/openzaak/setup_configuration/data.yaml`
|
||||||
|
- `backend/openzaak/bootstrap-catalogus.sh` (trim to whatever remains)
|
||||||
|
- `backend/openzaak/docker-compose.openzaak.yml`
|
||||||
|
- `backend/openzaak/README.md`
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Express the catalogus/zaaktype/statustype/roltype definitions currently created by
|
||||||
|
curl as `setup_configuration` YAML.
|
||||||
|
2. Wire it into the `web-init` command alongside the existing JWTSecret/Applicatie config.
|
||||||
|
3. Trim `bootstrap-catalogus.sh` to only what setup_configuration can't cover, if anything.
|
||||||
|
4. Test: tear down + `docker compose up` twice in a row (fresh volume, then existing
|
||||||
|
volume); confirm no failure on rerun.
|
||||||
|
5. Update the README describing the provisioning flow.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] Catalogus/zaaktype/statustype/roltype provisioning is declarative
|
||||||
|
(`setup_configuration`), not imperative curl.
|
||||||
|
- [ ] Running the compose stack up twice in a row doesn't error.
|
||||||
|
- [ ] WP-54's `OpenZaakIntegrationTests` still pass unchanged (same content, different
|
||||||
|
provisioning mechanism).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`docker compose -f backend/openzaak/docker-compose.openzaak.yml up` twice in a row (fresh
|
||||||
|
volume, then existing volume); `cd backend && dotnet test --filter Category=Integration`
|
||||||
|
against the harness.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Secrets/TLS (WP-55, unrelated but sequenced first in the table only by number), client
|
||||||
|
scopes (WP-57), notifications (WP-58).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
OpenZaak's `setup_configuration` coverage for zaaktype/besluittype content may be
|
||||||
|
incomplete upstream — if a piece genuinely can't be expressed declaratively, keep it in a
|
||||||
|
clearly-labeled idempotent script rather than forcing a bad fit.
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# WP-57 — Least-privilege client scopes
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 10 — OpenZaak production hardening
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The harness's OpenZaak client is granted `heeft_alle_autorisaties: true` in
|
||||||
|
`setup_configuration/data.yaml` — acceptable for a disposable test rig, wrong for anything
|
||||||
|
closer to production, where the BFF's client should hold only the Autorisaties it actually
|
||||||
|
exercises.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- `backend/openzaak/setup_configuration/data.yaml`
|
||||||
|
- `backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs` and `OpenZaakDocumentSource.cs`
|
||||||
|
(the actual ZGW endpoints/verbs called)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Scope precisely to what the BFF calls today: zaken (aanmaken, bijwerken, lezen),
|
||||||
|
statussen (aanmaken), rollen (aanmaken), documenten/zaakinformatieobjecten (aanmaken,
|
||||||
|
lezen) — enumerate exactly at kickoff from the client code, don't guess broader.
|
||||||
|
- No wildcard/all-scope grant in any environment beyond the pre-WP-56 disposable dev rig.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `backend/openzaak/setup_configuration/data.yaml` (Autorisaties block)
|
||||||
|
- `backend/openzaak/README.md`
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Grep `OpenZaakZaakSource.cs` and `OpenZaakDocumentSource.cs` for every ZGW
|
||||||
|
endpoint/verb called.
|
||||||
|
2. Replace `heeft_alle_autorisaties: true` with an explicit `autorisaties` list matching
|
||||||
|
exactly that set.
|
||||||
|
3. Re-run the full integration suite against the narrowed client; add any scope a 403
|
||||||
|
surfaces.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] Client config has no wildcard/all-scopes grant.
|
||||||
|
- [ ] `OpenZaakIntegrationTests` (WP-54) pass unchanged against the narrowed client.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`cd backend && dotnet test --filter Category=Integration` against the harness with the
|
||||||
|
narrowed client.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Rotating/expiring the client credential itself — defer until multi-tenant/production ops
|
||||||
|
actually need it.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
An overlooked scope only surfaces as a runtime 403 against a real instance — mitigated by
|
||||||
|
running the full integration suite, which already exercises every current call path
|
||||||
|
(WP-54).
|
||||||
|
|
||||||
|
Depends on: WP-56 (provisioning mechanism this scopes down).
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# WP-58 — Real notifications (celery + scripted abonnement)
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 10 — OpenZaak production hardening
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The WP-54 harness deliberately trims celery/celery-beat/celery-flower and nginx, and sets
|
||||||
|
`NOTIFICATIONS_DISABLED: 'true'` — without a Celery worker, OpenZaak 500s and rolls back on
|
||||||
|
every write to a notified resource. Fine for a fixture-driven integration harness; a real
|
||||||
|
deployment that wants live Notificaties (WP-52's webhook) needs the workers running and the
|
||||||
|
`abonnement` (subscription) actually registered against the BFF's public callback URL —
|
||||||
|
today that registration step is manual.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- `backend/openzaak/docker-compose.openzaak.yml` (top-of-file ponytail note)
|
||||||
|
- [openzaak-integration.md](../reference/openzaak-integration.md) (Notificaties section)
|
||||||
|
- [WP-52](WP-52-openzaak-notificaties.md)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Add celery + celery-beat as additional compose services (same `openzaak/open-zaak`
|
||||||
|
image, different command), pointed at the same redis broker already in the harness.
|
||||||
|
- Registering the `abonnement` becomes a scripted, idempotent step — not a manual
|
||||||
|
admin-UI action — parameterized by the BFF's real public URL.
|
||||||
|
- Keep the existing WP-54 harness variant (`NOTIFICATIONS_DISABLED: 'true'`) available for
|
||||||
|
fast local iteration where a live webhook round-trip isn't needed; this WP is additive
|
||||||
|
(a "with notifications" profile/override), not a replacement.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `backend/openzaak/docker-compose.openzaak.yml` (or an override file)
|
||||||
|
- New script/config for `abonnement` registration
|
||||||
|
- `docs/reference/openzaak-integration.md`
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Add celery/celery-beat services to a notifications-enabled compose profile.
|
||||||
|
2. Flip `NOTIFICATIONS_DISABLED` off for that profile.
|
||||||
|
3. Script the `abonnement` registration (POST to the NRC, pointed at the BFF's
|
||||||
|
`/zgw/notificaties` endpoint from WP-52), idempotent on rerun.
|
||||||
|
4. Verify a real write (e.g. a status change) triggers a live webhook delivery to the BFF.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] A notifications-enabled harness profile runs celery/celery-beat and delivers a real
|
||||||
|
notification end-to-end to the BFF's webhook.
|
||||||
|
- [ ] The `abonnement` registration step is a script, re-runnable without erroring on an
|
||||||
|
already-registered subscription.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Bring up the notifications-enabled profile; create a zaak/status change; confirm the BFF's
|
||||||
|
`/zgw/notificaties` endpoint receives and logs it.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Cache invalidation on notification receipt (flagged separately in
|
||||||
|
`openzaak-integration.md` as a `ponytail:` marker, not part of this slice);
|
||||||
|
celery-flower/monitoring UI.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
Celery/celery-beat add real operational surface (another process to keep alive) — scope
|
||||||
|
this WP to "works, documented," not a fully monitored deployment.
|
||||||
|
|
||||||
|
Depends on: WP-56 (provisioning mechanism this extends).
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# WP-59 — Per-document-type confidentialiteit config
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 10 — OpenZaak production hardening
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
`OpenZaakDocumentSource` hardcodes `vertrouwelijkheidaanduiding` to `"openbaar"` for every
|
||||||
|
uploaded document, regardless of document type. Real BIG-register documents (diploma's, ID
|
||||||
|
scans) plausibly need different confidentiality levels. This repo already has a house
|
||||||
|
pattern for exactly this kind of business-tunable value — stamdata-as-code (ADR-0004) — so
|
||||||
|
this slice is "apply the existing pattern," not invent a new one.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- [ADR-0004 — Stamdata as code](../reference/architecture/0004-stamdata-as-code.md)
|
||||||
|
- `backend/src/BigRegister.Api/Stamdata/` (an existing table for the shape to imitate)
|
||||||
|
- `backend/src/BigRegister.Api/Zgw/OpenZaakDocumentSource.cs`
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Confidentiality level is keyed by document type (whatever type already distinguishes
|
||||||
|
uploads, e.g. diploma vs. id-bewijs) via a new Stamdata table, using the existing
|
||||||
|
`StamdataTable.Of<T>` mechanism — not a new ad hoc config format.
|
||||||
|
- Default/fallback value stays `"openbaar"` if a document type isn't in the table, to
|
||||||
|
avoid a silent upload failure.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `Stamdata/` (new table + validation)
|
||||||
|
- `Zgw/OpenZaakDocumentSource.cs`
|
||||||
|
- `StamdataCatalog.cs` (register the new table)
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Add a `DocumentConfidentialiteit` stamdata table (document type →
|
||||||
|
vertrouwelijkheidaanduiding), validated at build like every other stamdata table
|
||||||
|
(`StamdataValidationTests`).
|
||||||
|
2. Register it in `StamdataCatalog` so it's editable via the existing `/beheer/stamdata`
|
||||||
|
grid.
|
||||||
|
3. `OpenZaakDocumentSource` looks up the level by document type instead of hardcoding
|
||||||
|
`"openbaar"`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] Confidentiality level for a real upload varies by document type per the new
|
||||||
|
stamdata table.
|
||||||
|
- [ ] `StamdataValidationTests` cover the new table (a bad edit fails CI, per ADR-0004).
|
||||||
|
- [ ] `/beheer/stamdata` can edit the new table without a code change (existing generic
|
||||||
|
editor).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`cd backend && dotnet test`; manual: `/beheer/stamdata` shows and edits the new table; an
|
||||||
|
upload for a mapped document type carries the mapped confidentiality level (test
|
||||||
|
asserted).
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Any UI-facing confidentiality display/change on the citizen side (FE keeps rendering
|
||||||
|
decisions, not recomputing them, per ADR-0001).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
None significant — this is a config/data-shape change reusing an established mechanism.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# WP-60 — Write-divergence resilience (local + ZGW writes)
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 10 — OpenZaak production hardening
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
A citizen action today does a local `Aanvraag`/`Document` write and a paired ZGW write
|
||||||
|
(create zaak/status/document); these aren't transactional. If the ZGW call fails after the
|
||||||
|
local write succeeds (or vice versa), the two diverge silently —
|
||||||
|
`openzaak-integration.md` flags this explicitly as "acceptable for a demo backend; a
|
||||||
|
production arc needs retry/reconciliation or an outbox." This is the one genuine
|
||||||
|
correctness gap standing between the current integration and something safe to call
|
||||||
|
production.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- [openzaak-integration.md](../reference/openzaak-integration.md) (the section discussing
|
||||||
|
this gap)
|
||||||
|
- `backend/src/BigRegister.Api/Data/ApplicationStore.cs`,
|
||||||
|
`Zgw/OpenZaakZaakSource.cs` (the two write sides)
|
||||||
|
- [ADR-0005 — OpenZaak behind the BFF](../reference/architecture/0005-openzaak-behind-bff.md)
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
Intentionally left open for kickoff — this is exactly the kind of ambiguous-root-cause,
|
||||||
|
multi-file design call the `planner` agent should make, not something pre-decided here.
|
||||||
|
Options to weigh at kickoff:
|
||||||
|
|
||||||
|
- (a) an outbox table — write local + an outbox row in one local transaction, a background
|
||||||
|
worker drains the outbox to ZGW with retry.
|
||||||
|
- (b) a simpler synchronous retry-with-backoff at the call site, plus a reconciliation job
|
||||||
|
that periodically diffs local vs. ZGW state and flags/repairs divergence.
|
||||||
|
|
||||||
|
Pick the smaller one that closes the gap — don't build a generic outbox framework if a
|
||||||
|
bounded retry+reconcile suffices for this POC's actual write volume.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
Likely `Data/ApplicationStore.cs`, a new reconciliation/outbox mechanism,
|
||||||
|
`Zgw/OpenZaakZaakSource.cs`, `Program.cs` (background job registration if needed).
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Design review with the `planner` agent — pick outbox vs. retry+reconcile.
|
||||||
|
2. Implement the chosen mechanism for the create-zaak and status-transition write paths.
|
||||||
|
3. Add a test that simulates a ZGW failure mid-write and asserts the system recovers
|
||||||
|
(retries successfully, or is left in a detectably-inconsistent-but-flagged state)
|
||||||
|
rather than silently diverging.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] A simulated ZGW failure after a successful local write no longer leaves permanent
|
||||||
|
silent divergence — either it retries to consistency or the divergence is
|
||||||
|
detectable/flagged.
|
||||||
|
- [ ] No new synchronous latency added to the happy path beyond what the chosen mechanism
|
||||||
|
requires.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
A new integration test that fails a stubbed ZGW call mid-write and asserts
|
||||||
|
recovery/flagging behavior; `cd backend && dotnet test`.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
A general-purpose outbox framework reusable beyond this one write pair (YAGNI unless a
|
||||||
|
second write pair appears — note WP-66 is exactly that second pair, so revisit scope if
|
||||||
|
WP-66 lands first); UI surfacing of reconciliation state (backend-only fix for now).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
Over-building this (a generic outbox/saga framework) for a POC's actual write volume —
|
||||||
|
ladder check at kickoff: does a bounded retry + periodic reconcile job cover it before
|
||||||
|
reaching for an outbox table?
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# WP-61 — Bootstrap the behandelportal app
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 11 — Behandelportal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
ADR-0002 already designed the Behandelaar/backoffice as a separate sibling frontend app,
|
||||||
|
not a folder in this repo. Nothing exists yet — `/beheer/zaken` is confirmed to be only a
|
||||||
|
cross-owner list+delete, no treatment workflow. The `new-ssp` skill exists precisely to
|
||||||
|
bootstrap a new portal from this template; this slice is running that recipe for real,
|
||||||
|
with no business context yet — an empty, correctly-scaffolded shell.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- `.claude/skills/new-ssp/SKILL.md`
|
||||||
|
- [ADR-0002 — user groups & bounded contexts](../reference/architecture/0002-user-groups-and-bounded-contexts.md)
|
||||||
|
- [ADR-0001 — BFF-lite decision DTOs](../reference/architecture/0001-bff-lite-decision-dtos.md)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Follow `new-ssp`'s own "keep vs. strip" checklist as-is: keep the `shared/ui` kernel,
|
||||||
|
tooling/CI gates, ADRs 0001-0003; strip the four citizen contexts and citizen branding.
|
||||||
|
- The new app talks to the same `BigRegister.Api` backend — no new backend service
|
||||||
|
(confirmed by ADR-0002: contexts integrate through the backend).
|
||||||
|
- Repo layout for the new app (separate repo vs. a second app in this monorepo) — decide
|
||||||
|
at kickoff based on how `new-ssp` is meant to be invoked.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
Whatever `new-ssp`'s recipe touches (new app root, `package.json`, shared/ui copy or
|
||||||
|
workspace reference, CI config) — enumerate at kickoff by following the skill.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Run the `new-ssp` bootstrap per its own checklist.
|
||||||
|
2. Confirm the known un-genericizable rough edges it flags (`shared/ui/debug-state/`, the
|
||||||
|
`/dashboard` route) are handled per the skill's own guidance (delete / TODO stopgap)
|
||||||
|
rather than re-solved from scratch.
|
||||||
|
3. Land an empty landing/login page only — no `behandeling` context yet (that's WP-64+).
|
||||||
|
4. Get the new app's own CI green.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] New app boots and its own `npm run ci` is green.
|
||||||
|
- [ ] No citizen-facing business context (`registratie`, `herregistratie`, `brief`,
|
||||||
|
`showcase`) present.
|
||||||
|
- [ ] Points at the same backend (`BigRegister.Api`) as this repo, no new backend stood
|
||||||
|
up.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`npm run ci` in the new app; manual smoke — app loads to an empty shell page.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Any `behandeling` screens (WP-64/65), identity (WP-62), status lifecycle (WP-63).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
`new-ssp`'s own docs already name its rough edges (`debug-state`, `/dashboard`) — budget
|
||||||
|
time for those rather than being surprised by them.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# WP-62 — Backend: medewerker caller identity + authz seam
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 11 — Behandelportal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The backend's only identity today is `CallerIdentity` (BSN + display name, from WP-53)
|
||||||
|
modeling a single zorgverlener actor. ADR-0002 requires a second actor kind
|
||||||
|
(medewerker/employee) that authenticates differently (no BSN, has `rollen`) and needs its
|
||||||
|
own capability checks for backoffice calls. This slice adds that identity + authz surface
|
||||||
|
on the backend only — unused by any frontend until WP-64 calls it, matching the same
|
||||||
|
"seam, not provider" discipline WP-53 used for citizen identity (stub, no real employee
|
||||||
|
SSO — out of scope per CLAUDE.md, same as DigiD).
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- [ADR-0002 §3 — Principal union](../reference/architecture/0002-user-groups-and-bounded-contexts.md)
|
||||||
|
- `backend/src/BigRegister.Api/Domain/Authorization/CallerIdentity.cs`,
|
||||||
|
`IIdentityProvider.cs`, `StubIdentityProvider.cs` (WP-53's pattern to extend/mirror)
|
||||||
|
- [WP-53](WP-53-inbound-identity-and-citizen-scoping.md)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Model the two actor kinds as a discriminated union (mirroring ADR-0002 §3:
|
||||||
|
`{ kind: 'zorgverlener'; bsn }` | `{ kind: 'medewerker'; medewerkerId; rollen }`),
|
||||||
|
backend-side, extending `CallerIdentity` rather than introducing a parallel type.
|
||||||
|
- Stub the medewerker identity the same way WP-53 stubbed citizen identity (a
|
||||||
|
header-driven `StubIdentityProvider` variant) — no real employee SSO/eHerkenning.
|
||||||
|
- New capability checks (e.g. `canBeoordelen`) are computed backend-side and exposed only
|
||||||
|
as decision flags, never a permission matrix shipped to a frontend (ADR-0001 discipline,
|
||||||
|
reaffirmed by ADR-0002 §3).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `Domain/Authorization/CallerIdentity.cs` (extend to the union)
|
||||||
|
- `Domain/Authorization/StubIdentityProvider.cs` (medewerker variant)
|
||||||
|
- `Domain/Authorization/Authz.cs` (medewerker capability checks)
|
||||||
|
- Tests
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Extend `CallerIdentity` to the two-actor-kind union.
|
||||||
|
2. Extend the stub identity provider to produce a `medewerker` identity from a
|
||||||
|
header/config, alongside the existing zorgverlener stub.
|
||||||
|
3. Add capability checks a backoffice caller needs (start with `canBeoordelen`; extend as
|
||||||
|
WP-65 needs more).
|
||||||
|
4. Unit tests for both identity kinds and the new capability checks — no consumer exists
|
||||||
|
yet (WP-64+ will call this).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `CallerIdentity` represents both actor kinds without breaking any existing
|
||||||
|
zorgverlener call site (WP-53's tests still green).
|
||||||
|
- [ ] A stub medewerker identity resolves from a request header, mirroring the existing
|
||||||
|
citizen stub.
|
||||||
|
- [ ] At least one capability flag (`canBeoordelen`) computable for a medewerker
|
||||||
|
identity, unit-tested.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`cd backend && dotnet test` (existing WP-53 tests unaffected + new medewerker tests
|
||||||
|
green).
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Any actual backoffice endpoint using this (WP-64+); real employee SSO/eHerkenning.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
If the union is modeled as a bolt-on rather than replacing the flat type, existing
|
||||||
|
zorgverlener call sites could break — mitigated by keeping WP-53's existing tests as a
|
||||||
|
regression gate.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# WP-63 — Backend: aanvraag status lifecycle as a published DTO
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 11 — Behandelportal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The FE currently infers "in behandeling" from a single boolean, `pendingHerregistratie`
|
||||||
|
(`big-profile.store.ts:53`) — explicitly called out in ADR-0002 as "a temporary stand-in
|
||||||
|
for a real, backend-owned status." The full lifecycle (`Ingediend → In behandeling →
|
||||||
|
(Meer info gevraagd ⇄) → Goedgekeurd/Afgewezen`) needs to become a real backend-published
|
||||||
|
value before either frontend can render it meaningfully — the SSP needs it as a richer
|
||||||
|
read (this WP), the behandelportal needs it as the thing it advances (WP-65).
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- [ADR-0002](../reference/architecture/0002-user-groups-and-bounded-contexts.md) (status
|
||||||
|
lifecycle diagram)
|
||||||
|
- `src/app/registratie/application/big-profile.store.ts` (the current boolean)
|
||||||
|
- [ADR-0001 — BFF-lite decision DTOs](../reference/architecture/0001-bff-lite-decision-dtos.md)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Status lives on the existing `Aanvraag`/`ApplicationSummaryDto` aggregate (extend,
|
||||||
|
don't invent a parallel status resource).
|
||||||
|
- The DTO change is additive: the SSP's `pendingHerregistratie` boolean can be derived
|
||||||
|
from the new status field (or kept as a computed convenience) so this ships with zero
|
||||||
|
required FE behavior change — a pure backend + contract widening.
|
||||||
|
- Only the status *value* is published here; any transition (advancing it) is a separate
|
||||||
|
write endpoint, not part of this slice (that's WP-65's mutation).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `Data/ApplicationStore.cs` (status field/enum)
|
||||||
|
- `Contracts/Dtos.cs` (extend `ApplicationSummaryDto`/status DTO)
|
||||||
|
- The FE `infrastructure/*.adapter.ts` + `parse*` boundary consuming it
|
||||||
|
- `big-profile.store.ts` (derive the existing boolean from the new field)
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Model the full status enum backend-side (`Ingediend`, `InBehandeling`,
|
||||||
|
`MeerInfoGevraagd`, `Goedgekeurd`, `Afgewezen`) on `Aanvraag`.
|
||||||
|
2. Publish it on the existing DTO the SSP already consumes.
|
||||||
|
3. Regenerate the typed client (`npm run gen:api`); update the FE `parse*` boundary to
|
||||||
|
read the new field.
|
||||||
|
4. Point `pendingHerregistratie` (or its replacement) at the new field so the SSP's
|
||||||
|
existing behavior is unchanged, just backed by a real value.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] Backend publishes the full status lifecycle value on the existing aanvraag DTO.
|
||||||
|
- [ ] `npm run gen:api` leaves no drift; SSP's existing "pending" display is unchanged in
|
||||||
|
behavior, now backed by the real status.
|
||||||
|
- [ ] `dotnet test` + `npm run ci` green.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`cd backend && dotnet test`; `npm run gen:api` (no drift); `npm run ci`; manual: SSP
|
||||||
|
dashboard still shows the same pending/approved states it does today.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Any endpoint that *advances* the status (WP-65); the behandelportal consuming it (WP-64).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
If the enum doesn't anticipate a state WP-65 needs (e.g. distinguishing who can transition
|
||||||
|
from what), it gets revised there — acceptable, this slice only needs to cover the states
|
||||||
|
already named in ADR-0002's diagram.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# WP-64 — Behandelportal: werkvoorraad (queue) screen
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 11 — Behandelportal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
First real screen in the new app — a read-only list of aanvragen needing treatment (the
|
||||||
|
"werkvoorraad"), gated by the medewerker identity from WP-62 and backed by the real status
|
||||||
|
DTO from WP-63. This is the smallest useful vertical slice of actual case-treatment
|
||||||
|
functionality — usable and demoable on its own, even before any decision can be recorded
|
||||||
|
(WP-65).
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- WP-61/62/63 outcomes
|
||||||
|
- `bff-endpoint` skill (screen-shaped decision DTO recipe)
|
||||||
|
- `src/app/registratie/ui/admin-cases.page.ts` (the existing cross-owner list, for what
|
||||||
|
to avoid repeating — that page is audit/delete, this one is a queue)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- New BFF-lite endpoint (decision-enriched DTO) shaped for a werkvoorraad screen — not a
|
||||||
|
reuse of the existing `/admin/cases` endpoint, which is audit-shaped, not queue-shaped
|
||||||
|
(per CLAUDE.md's per-screen endpoint discipline).
|
||||||
|
- Gated by the `canBeoordelen`-style capability from WP-62, not a new ad hoc role check.
|
||||||
|
- Domain first, then infrastructure, application, UI — per the house `new-feature`
|
||||||
|
recipe.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
New backend endpoint + DTO in `BigRegister.Api`; new `behandeling` context in the
|
||||||
|
behandelportal app (domain/infrastructure/application/ui per the house layering).
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Backend: new screen-shaped endpoint returning aanvragen needing treatment
|
||||||
|
(status = `InBehandeling`/`Ingediend`), gated by WP-62's capability.
|
||||||
|
2. FE: scaffold the `behandeling` context (domain → infrastructure → application → ui),
|
||||||
|
following `new-feature`.
|
||||||
|
3. UI: a list page (queue), composed from the shared `shared/ui` kernel — no new atoms
|
||||||
|
unless nothing existing fits.
|
||||||
|
4. Storybook story for the new list component/page, a11y-checked.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] Werkvoorraad screen lists aanvragen needing treatment for an authenticated
|
||||||
|
medewerker.
|
||||||
|
- [ ] `npm run ci` green in the behandelportal app; Storybook story present.
|
||||||
|
- [ ] Endpoint follows BFF-lite discipline (decision-enriched, not raw passthrough).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`npm run ci` in the behandelportal app; `cd backend && dotnet test`; manual: log in as a
|
||||||
|
stub medewerker, see the queue populated from seeded aanvragen.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Viewing a single zaak's detail (WP-65); recording any decision (WP-65).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
None major — this is a read-only composition slice once WP-61-63 exist.
|
||||||
|
|
||||||
|
Depends on: WP-61, WP-62, WP-63.
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# WP-65 — Behandelportal: zaak detail + beoordeling (decision) screen
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 11 — Behandelportal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The core case-treatment write path — a medewerker opens one aanvraag's detail (including
|
||||||
|
its documents) and records a decision (goedkeuren/afwijzen/meer info opvragen), advancing
|
||||||
|
the status lifecycle WP-63 published. This is the first genuinely new *write* capability
|
||||||
|
in the system beyond what the citizen SSP already does to itself.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- `mutation-command` skill
|
||||||
|
- `form-machine` skill (the decision action is a state-changing form, same idiom as
|
||||||
|
everywhere else in this house)
|
||||||
|
- [WP-63](WP-63-aanvraag-status-lifecycle.md) (the status field being advanced)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- The decision action is modeled as a `*.machine.ts` (Model/Msg/reduce) + a `submit-*`
|
||||||
|
command returning `Result`, per this house's one-idiom-for-forms rule — not a
|
||||||
|
hand-rolled mutable field.
|
||||||
|
- The mutation endpoint is a new BFF-lite write (per `mutation-command` recipe) that
|
||||||
|
transitions the status field from WP-63; it validates the transition is legal
|
||||||
|
server-side (e.g. can't approve an already-approved case) — the backend remains the
|
||||||
|
authority.
|
||||||
|
- Runs against `LocalZaakSource` for this slice; wiring the decision into real OpenZaak is
|
||||||
|
explicitly WP-66, not bundled here — keeps this slice's surface to app-level behavior
|
||||||
|
only.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
New mutation endpoint + command in `BigRegister.Api`; `behandeling/ui` detail page +
|
||||||
|
`behandeling/application` decision machine in the behandelportal app.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Backend: mutation endpoint advancing aanvraag status (goedkeuren/afwijzen/meer-info-
|
||||||
|
opvragen), validating the transition.
|
||||||
|
2. FE: zaak-detail page (documents + current status) + a decision form machine + submit
|
||||||
|
command.
|
||||||
|
3. Wire the werkvoorraad list (WP-64) to link into this detail page.
|
||||||
|
4. Storybook stories + a11y for the new detail/decision UI.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] A medewerker can view one aanvraag's detail and record a decision that advances its
|
||||||
|
status.
|
||||||
|
- [ ] Illegal transitions are rejected server-side (tested).
|
||||||
|
- [ ] End-to-end smoke: werkvoorraad → detail → decision → status change reflected back
|
||||||
|
in the queue.
|
||||||
|
- [ ] `npm run ci` (behandelportal app) + `dotnet test` green.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Manual/automated smoke test of the full werkvoorraad → beoordeling → besluit flow against
|
||||||
|
`LocalZaakSource`; `npm run ci`; `cd backend && dotnet test`.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Writing the decision to real OpenZaak (WP-66).
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
This is the largest FE slice in Phase 11 — if it feels too big at kickoff, split
|
||||||
|
detail-view (read) from decision-recording (write) into two sessions; the WP as scoped
|
||||||
|
already keeps them in one slice because a detail view with no decision action isn't
|
||||||
|
independently useful for a caseworker.
|
||||||
|
|
||||||
|
Depends on: WP-64.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# WP-66 — Wire the decision into OpenZaak
|
||||||
|
|
||||||
|
Status: todo
|
||||||
|
Phase: 11 — Behandelportal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
WP-65's decision currently only updates local state (`LocalZaakSource`). For the
|
||||||
|
behandelportal to actually function against a real register, the recorded decision needs
|
||||||
|
to also write a besluit/status transition to ZGW — extending the write capability that
|
||||||
|
already partially exists (`CreateStatusRequest`, `CreateRolRequest` in
|
||||||
|
`OpenZaakZaakSource.cs`) rather than building a new ZGW client from scratch.
|
||||||
|
|
||||||
|
## Read first
|
||||||
|
|
||||||
|
- `backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs` (existing write records)
|
||||||
|
- ZGW's Besluiten API (referenced in `openzaak-integration.md` if covered, or the ZGW
|
||||||
|
standard docs) for besluit creation
|
||||||
|
- [WP-50](WP-50-openzaak-create-zaak.md) (the first ZGW write slice, for the pattern to
|
||||||
|
follow)
|
||||||
|
|
||||||
|
## Decisions (pre-made, don't relitigate)
|
||||||
|
|
||||||
|
- Extend `IZaakSource`/`OpenZaakZaakSource` with a besluit/status-transition write,
|
||||||
|
following the same pattern WP-50 established for create-zaak (a records + mapper
|
||||||
|
addition, not a new abstraction).
|
||||||
|
- Gated by `Zgw:Enabled` like every other ZGW write — the behandelportal keeps working
|
||||||
|
against `LocalZaakSource` when it's off.
|
||||||
|
- Best done after Phase 10's WP-60 (write-divergence resilience) lands, since this is
|
||||||
|
exactly the second write pair that resilience work should already cover — but not
|
||||||
|
strictly blocked on it if Phase 10 is still in progress (call out the residual risk
|
||||||
|
explicitly if shipped first).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
`Zgw/OpenZaakZaakSource.cs` (besluit/status write), `Data/IZaakSource.cs` (new write
|
||||||
|
method), tests.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Add the besluit/status-transition write to `OpenZaakZaakSource`, mirroring WP-50's
|
||||||
|
create-zaak pattern.
|
||||||
|
2. Wire WP-65's decision command to call it when `Zgw:Enabled=true`.
|
||||||
|
3. Integration test against the WP-54 harness (extend `OpenZaakIntegrationTests`).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] A recorded decision writes a real besluit/status transition to OpenZaak when
|
||||||
|
`Zgw:Enabled=true`.
|
||||||
|
- [ ] Behandelportal still works unchanged against `LocalZaakSource` when
|
||||||
|
`Zgw:Enabled=false`.
|
||||||
|
- [ ] `OpenZaakIntegrationTests` covers the new write.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`cd backend && dotnet test --filter Category=Integration` against the (ideally
|
||||||
|
Phase-10-hardened) OpenZaak harness; manual smoke with `Zgw:Enabled=true`.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Any further behandelportal screens beyond beoordeling.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
If Phase 10's WP-60 (write-divergence resilience) hasn't landed yet, this introduces a
|
||||||
|
second unprotected write pair — call this out explicitly if the two phases aren't
|
||||||
|
sequenced together in practice.
|
||||||
|
|
||||||
|
Depends on: WP-65. Benefits from (but doesn't strictly require) WP-60.
|
||||||
Reference in New Issue
Block a user