diff --git a/backend/.gitignore b/backend/.gitignore index 55e0f04..eed92eb 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,7 +1,7 @@ bin/ obj/ -# WP-22: runtime SQLite file (+ WAL sidecars) — ship the migration, not the data. +# Runtime SQLite file (+ WAL sidecars) — ship the migration, not the data. bigregister.db bigregister.db-shm bigregister.db-wal diff --git a/backend/Dockerfile b/backend/Dockerfile index 4e41364..ecce932 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -# WP-30: lean deployable image (optional — not used by the dev demo, which keeps the SDK +# Lean deployable image (optional — not used by the dev demo, which keeps the SDK # image in the root docker-compose.yml for `dotnet run` hot-reload). Build from the repo # root: `docker build -f backend/Dockerfile .` FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build @@ -9,10 +9,10 @@ RUN dotnet publish backend/src/BigRegister.Api -c Release -o /app FROM mcr.microsoft.com/dotnet/aspnet:10.0 WORKDIR /app COPY --chown=$APP_UID:$APP_UID --from=build /app . -# LetterHtml.Render (WP-25) walks up from AppContext.BaseDirectory looking for a sibling +# LetterHtml.Render walks up from AppContext.BaseDirectory looking for a sibling # public/letter.css (the FE⇄BE letter contract) — this keeps that lookup working here too. COPY --chown=$APP_UID:$APP_UID public ./public # $APP_UID (uid/gid 1654, "app") is baked into this base image for exactly this purpose — -# non-root, and chown'd above so it can still create/write bigregister.db (WP-22) at /app. +# non-root, and chown'd above so it can still create/write bigregister.db at /app. USER $APP_UID ENTRYPOINT ["dotnet", "BigRegister.Api.dll"] diff --git a/backend/README.md b/backend/README.md index b55f692..a2423a5 100644 --- a/backend/README.md +++ b/backend/README.md @@ -15,7 +15,7 @@ status codes and error envelope are production-shaped. covers it, see `docker-compose.yml`) does **not** lose data. Delete the file to reset demo data back to empty, the same state a fresh clone starts from. This is a deliberate, right-sized choice for a POC (SQLite, no external DB service) — see -`docs/project/backlog/WP-22-durable-persistence.md`. +the durable-persistence design note in `docs/project/backlog/`. ## Run @@ -76,7 +76,7 @@ semantics) is introduced as **`/api/v2`** served alongside v1 until clients migr - `Diplomas/DiplomaRules.cs` — profession derivation + which policy questions apply. - `Registrations/HerregistratieRule.cs` — eligibility + reason + status invariant. - `Intake/IntakePolicy.cs` — scholing threshold + completeness re-validation on submit - (`RejectIncompleteScholing`, WP-69). + (`RejectIncompleteScholing`). - `Submissions/SubmissionRules.cs` — submit rejections + reference generation. ## Typed client (NSwag) diff --git a/backend/openzaak/README.md b/backend/openzaak/README.md index 6068fde..9d4c66d 100644 --- a/backend/openzaak/README.md +++ b/backend/openzaak/README.md @@ -1,4 +1,4 @@ -# OpenZaak integration harness (WP-54) +# OpenZaak integration harness A real OpenZaak, for developing/testing the ZGW seam (`backend/src/BigRegister.Api/Zgw/`) against something that isn't a fixture or a stub `HttpMessageHandler`. Deliberately **not** @@ -22,7 +22,7 @@ published), and one zaak (`BIG-2026-000123`) with an initiator rol for the seede (`111222333` — the same fixture BSN `OpenZaakZaakSourceTests.cs` uses). It writes what it seeded to `seeded.env` (gitignored) and prints a summary. -**Idempotent (WP-56)** — every resource is looked up by its natural key (the same field(s) +**Idempotent** — every resource is looked up by its natural key (the same field(s) OpenZaak enforces identity on: catalogus by `domein`+`rsin`, zaaktype by `catalogus`+ `identificatie`, statustype by `zaaktype`+`volgnummer`, roltype by `zaaktype`+ `omschrijvingGeneriek`, zaak by `identificatie`) before creating it, so re-running against an @@ -81,7 +81,7 @@ and is **excluded** from the default `dotnet test` run and from CI (`ci.yml`, `scripts/ci-local.sh` both filter `Category!=Integration`) — it only passes with this harness up, so it never runs where the harness doesn't exist. -## Notifications-enabled profile (WP-58) +## Notifications-enabled profile The base harness above never delivers a real notification (`NOTIFICATIONS_DISABLED: 'true'`, no celery worker) — fine for the read/write ZGW seam, not for proving a live webhook round-trip. @@ -137,7 +137,7 @@ already high going in; `ZGW_DEBUG_HTTP=1` on `api`, see `docker-compose.openzaak diagnostics to help nail the cause next time it reproduces). Prints the URLs to check afterward and the teardown commands. -Two caveats, both non-fatal (WP-60 catches and flags rather than surfacing an error): +Two caveats, both non-fatal (the BFF catches and flags rather than surfacing an error): **only `herregistratie` has a seeded zaaktype** here, so submit that wizard to prove a real write; and **no Documenten content is seeded**, so a document upload's ZGW half no-ops (pick "per post" in the wizard's document step, or ignore it). @@ -148,7 +148,7 @@ write; and **no Documenten content is seeded**, so a document upload's ZGW half docker compose -f docker-compose.openzaak.yml down -v ``` -## Production (WP-55) +## Production This dev harness stays dev-only: hardcoded `SECRET_KEY`, `POSTGRES_HOST_AUTH_METHOD=trust`, `IS_HTTPS: 'no'`, a client secret checked into `setup_configuration/data.yaml`. A real @@ -188,7 +188,7 @@ app change. Django migrations then `setup_configuration` against `setup_configuration/data.yaml`), and `web` (the OpenZaak API on `:8000`). Pinned to `openzaak/open-zaak:1.29.1`. No celery/celery-beat/celery-flower/nginx — trimmed for a lean, fast-booting harness; layer - `docker-compose.openzaak.notificaties.yml` (WP-58) on top for a real async notification + `docker-compose.openzaak.notificaties.yml` on top for a real async notification delivery round-trip. `NOTIFICATIONS_DISABLED=true` is required, not optional: without it, OpenZaak 500s (and **rolls back the whole create**) on any notified resource — see the compose file's comment. @@ -197,14 +197,14 @@ app change. one `bigregister-test` client with `heeft_alle_autorisaties: false` — this YAML mechanism (`vng_api_common`'s `ApplicatieConfigurationModel`) has no field for granular scopes at all, so the client starts with zero Autorisaties; `bootstrap-catalogus.sh` grants the exact ones - it needs (WP-57). + it needs. - `bootstrap-catalogus.sh` — the business content (catalogus/zaaktype/zaak/…) `setup_configuration` has no YAML for; every field value here was checked against OpenZaak's own OpenAPI spec and a live run of this exact script, not guessed (two OpenZaak quirks it works around: a zaaktype needs ≥1 resultaattype and 2 statustypen before it can be published, and its `selectielijstklasse` and the zaaktype's `selectielijstProcestype` must reference the same - `procesType` on the public VNG selectielijst API). Idempotent (WP-56) — see "Bring it up" above. - Also grants `bigregister-test`'s Autorisaties via `manage.py shell` (WP-57, see the script's + `procesType` on the public VNG selectielijst API). Idempotent — see "Bring it up" above. + Also grants `bigregister-test`'s Autorisaties via `manage.py shell` (see the script's top comment): `ztc` scopes (`catalogi.lezen`/`catalogi.schrijven`, this script's own content-creation needs) up front, `zrc` scopes (`zaken.aanmaken`/`zaken.bijwerken`/ `zaken.lezen`, scoped to the one zaaktype the BFF and this script both use) once that @@ -213,26 +213,26 @@ app change. grant (scoped to a real `informatieobjecttype`, which this script would also need to seed) when a later WP wires DRC content into this harness. - **Not here**: Documenten (DRC) content, or a real Notificaties API (NRC) — add DRC content if a - later WP needs to prove that round-trip against a live instance too (WP-51 is fixture-tested + later change needs to prove that round-trip against a live instance too (fixture-tested today). A real NRC is a separate application (`open-notificaties`) this harness deliberately - doesn't stand up — WP-58's notifications-enabled profile (below) proves live delivery without + doesn't stand up — the notifications-enabled profile (below) proves live delivery without one, since this harness only ever has one subscriber. -- `docker-compose.openzaak.notificaties.yml` (WP-58) — opt-in overlay: one celery worker for +- `docker-compose.openzaak.notificaties.yml` — opt-in overlay: one celery worker for OpenZaak (async notification delivery needs it) + `NOTIFICATIONS_DISABLED: 'false'`, joined to the repo root's own compose network so it can reach the `api` container by name (tried `host.docker.internal:host-gateway` first; this environment's rootless Podman doesn't route container→host-port traffic through it). See "Notifications-enabled profile" below. -- `bootstrap-notificaties.sh` (WP-58) — points OpenZaak's `NotificationsConfig` at the BFF's +- `bootstrap-notificaties.sh` — points OpenZaak's `NotificationsConfig` at the BFF's webhook via a `zgw_consumers.Service` (`update_or_create`, idempotent) instead of provisioning a real NRC `abonnement`; preflights that the BFF is reachable with the right secret first (a misconfigured target here means every write to a notified resource 500s and rolls back). -- `verify-notificatie.sh` (WP-58) — the runnable end-to-end check: PATCHes the seeded zaak, polls - the BFF's own `/admin/audit` (WP-41) for the resulting `zgw:notificatie`/`allow` row. -- `docker-compose.openzaak.prod.yml` (WP-55) — production overrides layered on top of +- `verify-notificatie.sh` — the runnable end-to-end check: PATCHes the seeded zaak, polls + the BFF's own `/admin/audit` for the resulting `zgw:notificatie`/`allow` row. +- `docker-compose.openzaak.prod.yml` — production overrides layered on top of `docker-compose.openzaak.yml`: real `SECRET_KEY`/DB password/site domain/allowed-hosts from required env vars (fails fast if unset), password DB auth instead of `trust`, `IS_HTTPS: 'yes'`. Adds no image/service of its own — see "Production" above for the full flow. -- `setup_configuration/data.prod.yaml.template` (WP-55) — the prod counterpart of `data.yaml` +- `setup_configuration/data.prod.yaml.template` — the prod counterpart of `data.yaml` with no secret in it (`${OPENZAAK_CLIENT_SECRET}` etc. as placeholders); `render-prod-secrets.sh` fills it in to the gitignored `data.prod.yaml`, which the prod compose override mounts over the container's `data.yaml`. diff --git a/backend/openzaak/bootstrap-catalogus.sh b/backend/openzaak/bootstrap-catalogus.sh index 96e79f9..6d4320b 100755 --- a/backend/openzaak/bootstrap-catalogus.sh +++ b/backend/openzaak/bootstrap-catalogus.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# WP-54 (seeding) / WP-56 (idempotency) — seeds business content +# Seeds business content # (catalogus/zaaktype/statustype/roltype/zaak/status/rol) into the OpenZaak harness started # by docker-compose.openzaak.yml. `setup_configuration/data.yaml` only covers infra config # (JWTSecret + Applicatie) — confirmed by reading the installed `django_setup_configuration` @@ -17,7 +17,7 @@ # zaak's `identificatie` + `url` on success; also writes them to seeded.env (repo-ignored) for # OpenZaakIntegrationTests.cs to assert against. # -# WP-57: `bigregister-test` starts with ZERO Autorisaties (data.yaml sets +# `bigregister-test` starts with ZERO Autorisaties (data.yaml sets # heeft_alle_autorisaties: false) — the setup_configuration YAML has no field for granular # scopes at all (confirmed from vng_api_common's own ApplicatieConfigurationModel), so this # script grants them itself via `manage.py shell` (Django ORM, inside the `web` container) at @@ -71,7 +71,7 @@ oz() { echo "$json" } -# Grant (replace) an Autorisatie for $CLIENT_ID directly via the ORM (see the WP-57 note up +# Grant (replace) an Autorisatie for $CLIENT_ID directly via the ORM (see the note up # top for why this bypasses the REST Autorisaties API). $1 = component, $2 = python list # literal of scopes, $3.. = extra `Autorisatie(...)` kwargs as `name=value` (value already a # valid Python literal, e.g. a quoted URL). @@ -168,7 +168,7 @@ print(json.dumps({ echo " created: $zaaktype_url" fi -echo "Granting zrc scopes (zaken.aanmaken, zaken.bijwerken, zaken.lezen, zaken.statussen.toevoegen), scoped to $zaaktype_url — the one zaaktype this harness (and the BFF's Zgw:ZaaktypeUrls config) ever uses. zaken.statussen.toevoegen is needed for WP-66's besluit write: zaken.aanmaken only covers the ONE status set at zaak creation, a later status (the besluit's eindstatus) needs this scope or OpenZaak 403s ('mag je slechts 1 status zetten')..." +echo "Granting zrc scopes (zaken.aanmaken, zaken.bijwerken, zaken.lezen, zaken.statussen.toevoegen), scoped to $zaaktype_url — the one zaaktype this harness (and the BFF's Zgw:ZaaktypeUrls config) ever uses. zaken.statussen.toevoegen is needed for the besluit write: zaken.aanmaken only covers the ONE status set at zaak creation, a later status (the besluit's eindstatus) needs this scope or OpenZaak 403s ('mag je slechts 1 status zetten')..." grant_scopes zrc '["zaken.aanmaken", "zaken.bijwerken", "zaken.lezen", "zaken.statussen.toevoegen"]' \ "zaaktype=\"$zaaktype_url\"" \ 'max_vertrouwelijkheidaanduiding="openbaar"' diff --git a/backend/openzaak/bootstrap-notificaties.sh b/backend/openzaak/bootstrap-notificaties.sh index 4eb762a..ae3ea59 100755 --- a/backend/openzaak/bootstrap-notificaties.sh +++ b/backend/openzaak/bootstrap-notificaties.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# WP-58 — points OpenZaak's own NotificationsConfig straight at this repo's BFF webhook -# (POST /api/v1/zgw/notificaties, WP-52) instead of standing up a real Notificaties API (NRC) +# Points OpenZaak's own NotificationsConfig straight at this repo's BFF webhook +# (POST /api/v1/zgw/notificaties) instead of standing up a real Notificaties API (NRC) # + abonnement — see docker-compose.openzaak.notificaties.yml's ponytail note for why. Requires # that overlay running (adds the celery worker + flips NOTIFICATIONS_DISABLED) AND the repo # root's own `docker compose up` running (the overlay joins its `api` container's network — @@ -41,7 +41,7 @@ from zgw_consumers.models import Service service, _ = Service.objects.update_or_create( slug="bff-webhook", defaults=dict( - label="BIG-register BFF webhook (WP-58)", + label="BIG-register BFF webhook", api_type=APITypes.orc, api_root="$BFF_API_ROOT", auth_type=AuthTypes.api_key, diff --git a/backend/openzaak/docker-compose.openzaak.bff.yml b/backend/openzaak/docker-compose.openzaak.bff.yml index b00b904..005159e 100644 --- a/backend/openzaak/docker-compose.openzaak.bff.yml +++ b/backend/openzaak/docker-compose.openzaak.bff.yml @@ -13,7 +13,7 @@ # # 1. Why container-to-container instead of `http://localhost:8000`: this dev environment's # rootless Podman drops container→host-port traffic through `host.docker.internal` -# (confirmed for the WP-58 notifications overlay's celery worker — DNS resolves it, every +# (confirmed for the notifications overlay's celery worker — DNS resolves it, every # TCP connect times out). # # 2. Why the ROOT project's `api` joins INTO this project's network (below), not the other way diff --git a/backend/openzaak/docker-compose.openzaak.notificaties.yml b/backend/openzaak/docker-compose.openzaak.notificaties.yml index 9210b1e..07dce85 100644 --- a/backend/openzaak/docker-compose.openzaak.notificaties.yml +++ b/backend/openzaak/docker-compose.openzaak.notificaties.yml @@ -1,9 +1,9 @@ -# WP-58 — notifications-enabled overlay, layered ON TOP of docker-compose.openzaak.yml +# Notifications-enabled overlay, layered ON TOP of docker-compose.openzaak.yml # (never alone): # # docker compose -f docker-compose.openzaak.yml -f docker-compose.openzaak.notificaties.yml up -d # -# The base file stays the WP-54 fast-iteration default (NOTIFICATIONS_DISABLED=true, no +# The base file stays the fast-iteration default (NOTIFICATIONS_DISABLED=true, no # worker) so nobody testing the read/write seam has to pull/boot this. This overlay flips # NOTIFICATIONS_DISABLED off and adds the one celery worker needed to actually deliver a # notification (see base file's ponytail note). @@ -14,7 +14,7 @@ # bootstrap-notificaties.sh points OpenZaak's NotificationsConfig straight at the BFF's webhook # instead — same delivery proof (a real write → a real HTTP POST → the BFF's audit trail), far # less harness to stand up and keep alive. Add a real NRC (+ abonnement/kanaal routing) if a -# later WP needs more than one subscriber or real kanaal-filtered fan-out. +# later change needs more than one subscriber or real kanaal-filtered fan-out. # # No celery-beat here: send_notification is a plain async task (client.post on save), not a # scheduled one — beat only matters on a real NRC's polling side, which this harness doesn't have. diff --git a/backend/openzaak/docker-compose.openzaak.prod.yml b/backend/openzaak/docker-compose.openzaak.prod.yml index 6528c89..7db3b6b 100644 --- a/backend/openzaak/docker-compose.openzaak.prod.yml +++ b/backend/openzaak/docker-compose.openzaak.prod.yml @@ -1,4 +1,4 @@ -# WP-55 — production overrides for docker-compose.openzaak.yml: real secrets, real DB auth, +# Production overrides for docker-compose.openzaak.yml: real secrets, real DB auth, # HTTPS-aware settings. Use ON TOP of the base file, never alone (it has no image/ports of its # own to add — see backend/openzaak/README.md for the required env vars and full flow): # diff --git a/backend/openzaak/docker-compose.openzaak.yml b/backend/openzaak/docker-compose.openzaak.yml index 3f6fb9f..98acce4 100644 --- a/backend/openzaak/docker-compose.openzaak.yml +++ b/backend/openzaak/docker-compose.openzaak.yml @@ -1,12 +1,12 @@ -# WP-54 — a real OpenZaak to develop/test the ZGW seam against, kept OUT of the root +# A real OpenZaak to develop/test the ZGW seam against, kept OUT of the root # docker-compose.yml on purpose (see backend/openzaak/README.md): OpenZaak is a full Django # stack (postgres + redis), heavy compared to this repo's own FE+BFF, and nobody who isn't # touching the ZGW slice should have to pull/boot it. # # ponytail: trimmed vs. open-zaak's own published compose — no celery/celery-beat/celery-flower # (async notification delivery, never asserted by the integration test) and no nginx (the test -# hits web's port directly). Add them back only if a later WP needs an actual notification -# round-trip against this harness (NRC delivery is already covered by fixture tests, WP-52). +# hits web's port directly). Add them back only if a later change needs an actual notification +# round-trip against this harness (NRC delivery is already covered by fixture tests). services: db: image: postgis/postgis:17-3.5 diff --git a/backend/openzaak/render-prod-secrets.sh b/backend/openzaak/render-prod-secrets.sh index cc7bf52..79b342e 100755 --- a/backend/openzaak/render-prod-secrets.sh +++ b/backend/openzaak/render-prod-secrets.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# WP-55 — renders setup_configuration/data.prod.yaml.template into the gitignored +# Renders setup_configuration/data.prod.yaml.template into the gitignored # data.prod.yaml docker-compose.openzaak.prod.yml mounts over the container's data.yaml. # Run this once before `docker compose ... up` in a production deploy; re-run whenever the # secrets rotate. Fails fast (no output file) if a required env var is missing — never diff --git a/backend/openzaak/setup_configuration/data.prod.yaml.template b/backend/openzaak/setup_configuration/data.prod.yaml.template index 2e85bcb..196f073 100644 --- a/backend/openzaak/setup_configuration/data.prod.yaml.template +++ b/backend/openzaak/setup_configuration/data.prod.yaml.template @@ -1,10 +1,10 @@ -# Prod counterpart of data.yaml (WP-54's dev-only version, kept as-is for local iteration — +# Prod counterpart of data.yaml (the dev-only version, kept as-is for local iteration — # see docker-compose.openzaak.yml's own comment on why it hardcodes a client secret). This # template has no secret in it; render-prod-secrets.sh substitutes OPENZAAK_CLIENT_SECRET # into it to produce the gitignored data.prod.yaml that docker-compose.openzaak.prod.yml # mounts over the container's data.yaml. # -# Least-privilege client scopes (WP-57): heeft_alle_autorisaties is false, matching the dev +# Least-privilege client scopes: heeft_alle_autorisaties is false, matching the dev # harness (setup_configuration has no YAML field for granular `autorisaties` — see # data.yaml's comment). This template only covers infra config; a real deploy must grant this # client's Autorisaties the same way bootstrap-catalogus.sh does for the dev harness — via diff --git a/backend/openzaak/setup_configuration/data.yaml b/backend/openzaak/setup_configuration/data.yaml index 20340f2..d2d1e7c 100644 --- a/backend/openzaak/setup_configuration/data.yaml +++ b/backend/openzaak/setup_configuration/data.yaml @@ -2,7 +2,7 @@ # documented CLI config mechanism — see docker-compose.openzaak.yml) instead of the Django # admin. Creates the ONE application the bootstrap script + integration test authenticate as. # -# heeft_alle_autorisaties is false (WP-57, least privilege) — but +# heeft_alle_autorisaties is false (least privilege) — but # `ApplicatieConfigurationModel` (vng_api_common's setup_configuration step) has no field for # granular `autorisaties` at all, only this boolean. So this client starts with ZERO scopes; # bootstrap-catalogus.sh grants the exact ones it needs via `manage.py shell` (Django ORM, @@ -12,7 +12,7 @@ sites_config_enable: true sites_config: items: - domain: localhost:8000 - name: OpenZaak (WP-54 harness) + name: OpenZaak (harness) vng_api_common_credentials_config_enable: true vng_api_common_credentials: @@ -26,5 +26,5 @@ vng_api_common_applicaties: - uuid: 5a09b3c9-6a54-4b2b-8f3c-1f9b6b6a3a01 client_ids: - bigregister-test - label: BIG-register BFF (WP-54 test harness) + label: BIG-register BFF (test harness) heeft_alle_autorisaties: false diff --git a/backend/openzaak/verify-notificatie.sh b/backend/openzaak/verify-notificatie.sh index 5b0ef4f..bce8cc7 100755 --- a/backend/openzaak/verify-notificatie.sh +++ b/backend/openzaak/verify-notificatie.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# WP-58 — proves the "real write -> real webhook delivery" round-trip end-to-end: PATCHes the +# Proves the "real write -> real webhook delivery" round-trip end-to-end: PATCHes the # zaak bootstrap-catalogus.sh seeded (a notified ZRC resource), then polls the BFF's own audit -# trail (WP-41) for the resulting `zgw:notificatie` row. Requires bootstrap-catalogus.sh and +# trail for the resulting `zgw:notificatie` row. Requires bootstrap-catalogus.sh and # bootstrap-notificaties.sh to have already run. set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" @@ -29,7 +29,7 @@ jwt() { printf '%s.%s' "$signing_input" "$sig" } -echo "Triggering a real write: PATCH $ZAAK_URL (bijwerken — WP-57 granted zaken.aanmaken" +echo "Triggering a real write: PATCH $ZAAK_URL (bijwerken — the client is granted zaken.aanmaken" echo "for exactly ONE status, so a second status create 403s; a zaak update is the write this" echo "client's narrowed scope can repeat)..." response=$(curl -sS -X PATCH -H "Authorization: Bearer $(jwt)" -H 'Content-Type: application/json' \ diff --git a/backend/src/BigRegister.Api/Contracts/Dtos.cs b/backend/src/BigRegister.Api/Contracts/Dtos.cs index 953e999..db31245 100644 --- a/backend/src/BigRegister.Api/Contracts/Dtos.cs +++ b/backend/src/BigRegister.Api/Contracts/Dtos.cs @@ -77,11 +77,11 @@ public sealed record DocumentRefDto(string CategoryId, string Channel, string? D public sealed record ChangeRequestRequest(string Telefoon); -// Authz/PII-reveal audit row (WP-41) — data-minimised, no PII (see AuthzAuditEntry). +// Authz/PII-reveal audit row — data-minimised, no PII (see AuthzAuditEntry). public sealed record AuthzAuditDto( string At, string Action, string Resource, string Decision, string Role, string CorrelationId); -// Feature flags (WP-47): the resolved flag set + the admin toggle body. +// Feature flags: the resolved flag set + the admin toggle body. public sealed record FeatureFlagDto(string Key, string Description, bool Enabled); public sealed record SetFeatureFlagRequest(bool Enabled); @@ -103,7 +103,7 @@ public sealed record AanvraagSummaryDto( string Id, string Type, AanvraagStatusDto Status, IReadOnlyList DocumentIds, string CreatedAt, string UpdatedAt, string? SubmittedAt, - string? Owner = null); // populated for the admin cross-owner list (WP-36); the user's own list ignores it + string? Owner = null); // populated for the admin cross-owner list; the user's own list ignores it public sealed record AanvraagDetailDto( string Id, string Type, AanvraagStatusDto Status, @@ -118,7 +118,7 @@ public sealed record DraftSyncRequest( IReadOnlyList? DocumentIds = null); // Submit carries only the fields the server re-validates per wizard type. -// AanvullendeScholing/ScholingPunten (WP-69) — intake-typed aanvragen only (gated by +// AanvullendeScholing/ScholingPunten — intake-typed aanvragen only (gated by // IntakePolicy.RejectIncompleteScholing's caller), null for the others. public sealed record AanvraagIndienenRequest( string? DiplomaHerkomst = null, int? Uren = null, @@ -127,7 +127,7 @@ public sealed record AanvraagIndienenRequest( public sealed record AanvraagIndienenResponse(string Referentie, AanvraagStatusDto Status); -// --- Beoordeling (WP-65): the behandelportal's case-detail screen. --- +// --- Beoordeling: the behandelportal's case-detail screen. --- public sealed record BeoordelingDocumentDto(string DocumentId, string CategoryId, string FileName); @@ -141,7 +141,7 @@ public sealed record BeoordelingViewDto( IReadOnlyList Documenten, BeoordelingDecisionsDto Decisions); -/// Recording a decision (WP-65b). `Besluit` is the enum member name as a string — same +/// Recording a decision. `Besluit` is the enum member name as a string — same /// wire convention as `AanvraagStatusDto.Tag` (this backend never ships a raw C# enum, /// it round-trips names via Enum.Parse/.ToString() at the Contracts boundary, no /// JsonStringEnumConverter configured). The endpoint 400s an unknown name. Toelichting @@ -199,7 +199,7 @@ public sealed record BriefDto( // BIG-nummer the case screen ships masked. Status-independent, unlike the action gates. public sealed record BriefDecisionsDto(bool CanEdit, bool CanApprove, bool CanReject, bool CanSend, bool CanRevealBigNummer); -// The brief's screen DTO also carries the org template it renders with (WP-23): +// The brief's screen DTO also carries the org template it renders with: // the sub-org's current PUBLISHED version — or, once sent, the version pinned at // send time (sent letters are immutable; a republish never re-renders them). // The case this letter is about — the zorgverlener + aanvraag the behandelaar is @@ -220,7 +220,7 @@ public sealed record RevealBigNummerResponse(string BigNummer); // PRD-0002 §6: coarse, role-derived capabilities for nav/menu-level checks. public sealed record MeDto(IReadOnlyList Capabilities); -// --- Organization templates (WP-23, Brief v2 PRD §3) --- +// --- Organization templates (Brief v2 PRD §3) --- // The second template axis: appearance/identity per sub-organization (letterhead, // footer, signature, margins). Orthogonal to the case-type template (sections + // placeholders); the two only meet at render time. diff --git a/backend/src/BigRegister.Api/Contracts/Mappers.cs b/backend/src/BigRegister.Api/Contracts/Mappers.cs index ce020c9..345fce1 100644 --- a/backend/src/BigRegister.Api/Contracts/Mappers.cs +++ b/backend/src/BigRegister.Api/Contracts/Mappers.cs @@ -43,15 +43,15 @@ public static class Mappers /// is the one place a status has no , so it becomes the wire /// convention's magic string here at the boundary rather than living inside the domain type. /// Shared by and ZgwZaakMapper, so both status producers - /// agree on the projection (WP-68 F3). + /// agree on the projection. public static AanvraagStatusDto ToDto(this AanvraagStatus s) => new( s.Tag?.ToString() ?? "Concept", s.StepIndex, s.StepCount, s.Referentie, s.Manual, s.Reden); // Aanvraag status is COMPUTED ON READ (see the StatusAt extension, Data/AanvraagMapper.cs) — - // this is now a one-line projection of that onto the wire DTO (WP-68 F3, WP-73). + // this is now a one-line projection of that onto the wire DTO. public static AanvraagStatusDto ToStatusDto(this Aanvraag a, DateTimeOffset now) => a.StatusAt(now).ToDto(); - /// SubmittedAt only exists once Submitted/Decided (WP-73) — null for a Concept, + /// SubmittedAt only exists once Submitted/Decided — null for a Concept, /// same as the wire DTO's own nullable field. private static string? SubmittedAtOf(Aanvraag a) => a switch { @@ -61,7 +61,7 @@ public static class Mappers _ => null, }; - /// Draft only exists pre-submission (WP-73) — null once Submitted/Decided (nothing + /// Draft only exists pre-submission — null once Submitted/Decided (nothing /// reads it past that point; see AanvraagMapper.ApplyTo's Submitted branch). private static JsonElement? DraftOf(Aanvraag a) => a is Aanvraag.Concept c ? c.Draft : null; @@ -69,10 +69,10 @@ public static class Mappers a.Id, a.Type, a.ToStatusDto(now), a.DocumentIds, a.CreatedAt.ToString("o"), a.UpdatedAt.ToString("o"), SubmittedAtOf(a)); - /// Admin summary — same shape plus the owner (WP-36; the user-facing list leaves Owner null). + /// Admin summary — same shape plus the owner (the user-facing list leaves Owner null). /// The owner is a BSN, and both consumers of this mapper are cross-owner lists read by /// someone who is not the subject (`/admin/cases`, `/werkvoorraad`), so it goes out masked - /// (RB-03/BIO-003). Masking here rather than at each endpoint means a third cross-owner + /// (BIO-003). Masking here rather than at each endpoint means a third cross-owner /// list cannot be added that forgets to. public static AanvraagSummaryDto ToAdminSummaryDto(this Aanvraag a, DateTimeOffset now) => a.ToSummaryDto(now) with { Owner = Pii.MaskTail(a.Owner, 3) }; diff --git a/backend/src/BigRegister.Api/Data/AanvraagMapper.cs b/backend/src/BigRegister.Api/Data/AanvraagMapper.cs index 104d195..e9ea50a 100644 --- a/backend/src/BigRegister.Api/Data/AanvraagMapper.cs +++ b/backend/src/BigRegister.Api/Data/AanvraagMapper.cs @@ -5,7 +5,7 @@ namespace BigRegister.Api.Data; /// /// The two-way seam between (the EF-mapped persistence row — /// mutable, no invariants of its own, exactly the shape SQLite needs) and -/// (the closed Concept/Submitted/Decided domain union, WP-73). is the +/// (the closed Concept/Submitted/Decided domain union). is the /// read half: it reconstructs whichever variant a row's stored fields describe, going through /// that variant's own constructor/required members, so a row that doesn't actually describe a /// legal aanvraag throws here rather than downstream. / @@ -36,7 +36,7 @@ public static class AanvraagMapper var submittedAt = row.SubmittedAt ?? throw new InvalidOperationException($"Submitted aanvraag {row.Id} has no SubmittedAt."); - // Reden wins over BesluitStatus — matches the pre-WP-73 StatusAt's own priority. In + // Reden wins over BesluitStatus — matches StatusAt's own established priority. In // practice a row never carries both (BeoordelingRules.CanDecide already refuses a besluit // once Reden's auto-reject makes the projected status Afgewezen), but if it somehow did, // the auto-reject at submission time is authoritative. @@ -133,7 +133,7 @@ public static class AanvraagMapper break; case Aanvraag.Submitted s: - // Submitted ⇒ !Draft (WP-73's Draft decision) — nothing reads a submitted aanvraag's + // Submitted ⇒ !Draft (the Draft-clearing decision) — nothing reads a submitted aanvraag's // draft (registratie/application/draft-sync.ts only ever resumes a still-Concept // wizard), so this is now actually true rather than the aspirational doc-comment it // used to be. @@ -186,11 +186,11 @@ public static class AanvraagMapper return row; } - /// The status at a point in time (WP-68 F3, WP-73) — pattern matching over the + /// The status at a point in time — pattern matching over the /// closed union, replacing the null-forgiving derefs the old flat /// mutable row needed (Referentie/SubmittedAt are simply non-nullable on Submitted/Decided /// now, so there's nothing left to force). A recorded decision wins over the auto-approve - /// computation, matching the pre-WP-73 priority. + /// computation, matching the established priority. public static AanvraagStatus StatusAt(this Aanvraag a, DateTimeOffset now) => a switch { Aanvraag.Concept c => AanvraagStatus.Concept(c.StepIndex, c.StepCount), diff --git a/backend/src/BigRegister.Api/Data/AppDbContext.cs b/backend/src/BigRegister.Api/Data/AppDbContext.cs index 08f109d..460fce0 100644 --- a/backend/src/BigRegister.Api/Data/AppDbContext.cs +++ b/backend/src/BigRegister.Api/Data/AppDbContext.cs @@ -7,7 +7,7 @@ namespace BigRegister.Api.Data; /// /// EF Core/SQLite persistence for the three stores that used to be static -/// in-memory dictionaries (WP-22): , +/// in-memory dictionaries: , /// + , and . Opaque nested shapes /// (a wizard's draft snapshot, a brief's sections/placeholders/status) are stored as /// JSON text columns rather than redesigned into relational tables — the backend diff --git a/backend/src/BigRegister.Api/Data/ApplicationStore.cs b/backend/src/BigRegister.Api/Data/ApplicationStore.cs index 6160148..7758ee2 100644 --- a/backend/src/BigRegister.Api/Data/ApplicationStore.cs +++ b/backend/src/BigRegister.Api/Data/ApplicationStore.cs @@ -6,7 +6,7 @@ using BigRegister.Domain.Submissions; namespace BigRegister.Api.Data; /// -/// The EF-mapped persistence row for an application (aanvraag) — WP-73 demoted this to +/// The EF-mapped persistence row for an application (aanvraag) — demoted to /// exactly that: a flat, mutable bag with no invariants of its own (SQLite needs precisely /// this shape), never read or written directly outside this file. Everywhere else, production /// code reads and writes (the closed Concept/Submitted/Decided domain @@ -32,21 +32,21 @@ public sealed class AanvraagEntity public DateTimeOffset UpdatedAt { get; set; } public DateTimeOffset? SubmittedAt { get; set; } - /// The OpenZaak zaak's URL, set once CreateZaak (WP-50) registers one — null under - /// the local source. Persisted so later steps (WP-51's document→zaak link) can find it + /// The OpenZaak zaak's URL, set once CreateZaak registers one — null under + /// the local source. Persisted so later steps (the document→zaak link) can find it /// without a network round-trip; IZaakSource.CreateZaak itself doesn't write here (the /// endpoint does, via ) to keep the seam's write /// surface at "return data", not "reach into another store". public string? ZaakUrl { get; set; } - /// WP-60: non-null means the ZGW side of this submit (or its document link) did not + /// Non-null means the ZGW side of this submit (or its document link) did not /// complete — the local aanvraag is authoritative and is NOT rolled back (that risks an /// orphan zaak if the failure landed after the zaak POST succeeded). The zaak, if it exists, /// is re-findable by identificatie == Referentie. Cleared by a future repair path; /// none exists yet (see openzaak-integration.md's "Write resilience" section). public string? ZgwError { get; set; } - /// WP-65b: a behandelaar's recorded decision, if any. Non-null wins over the + /// A behandelaar's recorded decision, if any. Non-null wins over the /// auto-approve computation in — /// "a recorded decision wins". Mutable across /// (a behandelaar may decide again later); frozen once Goedgekeurd/Afgewezen (terminal, per @@ -59,7 +59,7 @@ public sealed class AanvraagEntity } /// -/// EF Core/SQLite-backed application store (WP-22 — was a static Dictionary), +/// EF Core/SQLite-backed application store (was a static Dictionary), /// mirrors . ponytail: one global lock — SQLite /// tolerates only one writer at a time anyway, and this was already a single /// coarse gate before the DB existed. @@ -72,7 +72,7 @@ public static class ApplicationStore private static readonly object _gate = new(); /// Create a Concept for — UNLESS one of this - /// already exists unsubmitted. WP-35: at most one Concept per + /// already exists unsubmitted. At most one Concept per /// type is a server-enforced invariant (the FE's draft-sync only guards it best-effort; /// this stays procedural here — it's an AGGREGATE-SET rule over every (Owner, Type), not /// something a single Aanvraag value's own shape could ever encode, and there is no unique @@ -121,7 +121,7 @@ public static class ApplicationStore } } - /// Cross-owner single read (WP-65b) — the behandelaar decision endpoint's counterpart of + /// Cross-owner single read — the behandelaar decision endpoint's counterpart of /// , same "any owner" shape as . public static Aanvraag? GetAny(string id) { @@ -132,7 +132,7 @@ public static class ApplicationStore } } - /// Cross-owner lookup by Referentie — real bug fix (WP-66): the behandelaar besluit + /// Cross-owner lookup by Referentie — a real bug fix: the behandelaar besluit /// endpoint receives the FE-facing case id from IZaakSource.ListCases, which under /// OpenZaakZaakSource is the ZGW zaak's own uuid, NOT this store's primary key (only /// LocalZaakSource's id happens to already be the Aanvraag.Id — every besluit 404'd @@ -147,7 +147,7 @@ public static class ApplicationStore } } - /// Admin: every case across all owners (WP-36). The per-owner List is the norm; this + /// Admin: every case across all owners. The per-owner List is the norm; this /// is the deliberate cross-owner read behind the admin-only /admin/cases endpoint. public static IReadOnlyList ListAll() { @@ -163,7 +163,7 @@ public static class ApplicationStore /// Draft sync: idempotent upsert of the wizard snapshot. Only a Concept is mutable — the /// domain reconstruction below is what enforces "0 <= StepIndex <= StepCount" /// ('s own constructor throws on an out-of-range pair instead - /// of this silently writing one onto the row, the way the pre-WP-73 code did). + /// of this silently writing one onto the row, the way the earlier code did). public static bool SyncDraft(string id, string owner, JsonElement draft, int stepIndex, int stepCount, IReadOnlyList? documentIds) { lock (_gate) @@ -207,7 +207,7 @@ public static class ApplicationStore return true; } - /// Admin: delete ANY case regardless of owner or submitted state (WP-36). The + /// Admin: delete ANY case regardless of owner or submitted state. The /// user-facing Delete refuses a submitted aanvraag and is owner-scoped; an admin /// managing the register may remove any case. Cascades to the case's documents /// using its own owner. Returns false only when the id doesn't exist. @@ -231,7 +231,7 @@ public static class ApplicationStore /// Submit transition. reject != null → Afgewezen; else accepted (In behandeling, /// auto-advancing to Goedgekeurd after the window when autoApprovable). Returns null - /// if the aanvraag is gone or already submitted (idempotency guard). WP-73: the returned + /// if the aanvraag is gone or already submitted (idempotency guard). The returned /// is constructed with a non-null Referentie/SubmittedAt by /// its own required members — there is no longer a null-forgiving deref anywhere down the /// line reading them back (StatusAt, IZaakSource.CreateZaak). Submitting also @@ -267,7 +267,7 @@ public static class ApplicationStore } } - /// Persist the zaak URL CreateZaak (WP-50) registered for this aanvraag. No-op if + /// Persist the zaak URL CreateZaak registered for this aanvraag. No-op if /// the aanvraag is gone (shouldn't happen — this runs right after Submit found it). public static void SetZaakUrl(string id, string zaakUrl) { @@ -297,13 +297,13 @@ public static class ApplicationStore public enum RecordBesluitOutcome { Ok, NotFound, Conflict } - /// Record a behandelaar's decision (WP-65b) — cross-owner like + /// Record a behandelaar's decision — cross-owner like /// , since a behandelaar decides on any citizen's case. - /// WP-68 (F2): the transition-legality check () + /// The transition-legality check () /// now runs INSIDE this lock, against a status read fresh under the lock, rather than in /// the endpoint beforehand — two concurrent besluiten used to both pass the endpoint's /// check before either wrote, letting the second silently overwrite a terminal decision. - /// WP-73: / + /// / /// require a non-null Toelichting by their own shape — the endpoint already 400s a missing /// one (BeoordelingRules.RequiresToelichting), and this is the defense-in-depth /// backstop for any other caller (this method is public, and e.g. diff --git a/backend/src/BigRegister.Api/Data/AuthzAuditStore.cs b/backend/src/BigRegister.Api/Data/AuthzAuditStore.cs index 6f05bba..47d7bd9 100644 --- a/backend/src/BigRegister.Api/Data/AuthzAuditStore.cs +++ b/backend/src/BigRegister.Api/Data/AuthzAuditStore.cs @@ -1,7 +1,7 @@ namespace BigRegister.Api.Data; /// -/// A persisted, DATA-MINIMISED authorization/PII-reveal audit entry (WP-41, PRD-0002 §8): +/// A persisted, DATA-MINIMISED authorization/PII-reveal audit entry (PRD-0002 §8): /// who (acting role, not identity), what action, on which resource ref, allow or deny, and /// the correlation id — **never** a name, BSN, or the value that was (or wasn't) revealed. /// Id is EF Core's auto-increment key (not positional), mirroring . @@ -37,7 +37,7 @@ public static class AuthzAuditStore } } - /// Newest first. Ordered client-side: SQLite can't ORDER BY a DateTimeOffset (WP-36). + /// Newest first. Ordered client-side: SQLite can't ORDER BY a DateTimeOffset. public static IReadOnlyList List() { lock (_gate) diff --git a/backend/src/BigRegister.Api/Data/BriefStore.cs b/backend/src/BigRegister.Api/Data/BriefStore.cs index 170b3fd..69ecd04 100644 --- a/backend/src/BigRegister.Api/Data/BriefStore.cs +++ b/backend/src/BigRegister.Api/Data/BriefStore.cs @@ -7,7 +7,7 @@ namespace BigRegister.Api.Data; /// /// The letter (brief) — one demo brief per owner, created from a template on first -/// read. EF Core/SQLite-backed (WP-22 — was a static Dictionary), mirrors +/// read. EF Core/SQLite-backed (was a static Dictionary), mirrors /// . The status machine and its guards live here (the /// server is authoritative for transitions); the FE mirrors them in its pure reducer /// for UX. Rich-text content is stored opaquely as DTOs — the stub does not @@ -23,11 +23,11 @@ public sealed class BriefEntity public required IReadOnlyList Placeholders { get; init; } public List Sections { get; set; } = new(); public BriefStatusDto Status { get; set; } = new("draft"); - /// Which sub-organization's org template themes this letter (WP-23). + /// Which sub-organization's org template themes this letter. public string SubOrgId { get; set; } = OrgTemplateSeed.Registers; /// Pinned at send: sent letters are immutable, a republish never re-themes them. public int? SentOrgTemplateVersion { get; set; } - /// The composed HTML archived at send (WP-25) — from here on the preview endpoint + /// The composed HTML archived at send — from here on the preview endpoint /// serves this verbatim, so a later org-template republish never re-renders it. public string? ArchivedHtml { get; set; } @@ -47,7 +47,7 @@ public static class BriefStore private static readonly object _gate = new(); - /// Pure query (RB-23/CQ-007): no write. `GET /brief` 404s when this returns null — + /// Pure query (CQ-007): no write. `GET /brief` 404s when this returns null — /// the owner's first-ever draft is created only through the explicit `ResetAndCreate` /// command (`POST /brief/reset`), never as a side effect of a read. public static BriefEntity? Get(string owner) @@ -110,10 +110,10 @@ public static class BriefStore var outcome = BriefRules.CanSend(e.Status); if (outcome != Outcome.Ok) return (outcome, null); e.Status = new BriefStatusDto("sent", SentAt: at); - // Pin the org-template version the letter was sent with (WP-23): from here on + // Pin the org-template version the letter was sent with: from here on // its appearance is frozen — republishing the template touches unsent briefs only. e.SentOrgTemplateVersion = OrgTemplateStore.PublishedVersionOf(e.SubOrgId); - // Archive the composed HTML at this exact instant (WP-25): the preview endpoint + // Archive the composed HTML at this exact instant: the preview endpoint // serves this verbatim once sent, so a later republish never re-renders it. var template = OrgTemplateStore.TemplateForBrief(e.SubOrgId, e.SentOrgTemplateVersion); e.ArchivedHtml = LetterHtml.Render(e, template, at, watermark: false); diff --git a/backend/src/BigRegister.Api/Data/Db.cs b/backend/src/BigRegister.Api/Data/Db.cs index 10001c3..6838f1a 100644 --- a/backend/src/BigRegister.Api/Data/Db.cs +++ b/backend/src/BigRegister.Api/Data/Db.cs @@ -6,7 +6,7 @@ namespace BigRegister.Api.Data; /// /// Factory for short-lived instances. The three stores /// (ApplicationStore/DocumentStore/BriefStore) are static classes — that shape -/// predates WP-22 and this WP keeps it — so they can't take a constructor-injected +/// predates this persistence layer, which keeps that shape — so they can't take a constructor-injected /// DbContext; each store method opens one here, uses it, and disposes it under its /// own lock instead. /// diff --git a/backend/src/BigRegister.Api/Data/DocumentStore.cs b/backend/src/BigRegister.Api/Data/DocumentStore.cs index 96c6c0b..978d5f2 100644 --- a/backend/src/BigRegister.Api/Data/DocumentStore.cs +++ b/backend/src/BigRegister.Api/Data/DocumentStore.cs @@ -4,7 +4,7 @@ namespace BigRegister.Api.Data; /// /// Stored document: metadata + bytes. The demo persists bytes in the SQLite file -/// (WP-22) purely so a re-opened wizard can preview/download what was uploaded — a +/// purely so a re-opened wizard can preview/download what was uploaded — a /// real backend persists them to blob storage keyed by DocumentId. Bytes are never /// serialized into a JSON response; only the dedicated content endpoint streams them. /// @@ -14,7 +14,7 @@ public sealed record StoredDocument( { public bool Linked { get; set; } - /// The OpenZaak DRC enkelvoudiginformatieobject's URL, set once Upload (WP-51) + /// The OpenZaak DRC enkelvoudiginformatieobject's URL, set once Upload /// registers one — null under the local source. Persisted so the later zaak-link step can /// find it without re-uploading; not part of the positional constructor, same reasoning as /// (every existing `new StoredDocument(...)` call site keeps working). @@ -30,7 +30,7 @@ public sealed record AuditEntry(DateTimeOffset At, string Action, string Documen } /// -/// EF Core/SQLite-backed document store + audit log (WP-22 — was a static +/// EF Core/SQLite-backed document store + audit log (was a static /// Dictionary). ponytail: one global lock, same as before — SQLite tolerates only /// one writer at a time anyway, and this process already serialized all access /// through a single gate, so it now doubles as a coarse single-writer guard for @@ -85,7 +85,7 @@ public static class DocumentStore } } - /// Documents by DocumentId (WP-65's beoordeling detail reads an aanvraag's already- + /// Documents by DocumentId (the beoordeling detail reads an aanvraag's already- /// linked documents) — the DocumentId-keyed counterpart of , which is /// keyed by the wizard's own LocalId instead. public static IReadOnlyList ByIds(IEnumerable documentIds) @@ -114,7 +114,7 @@ public static class DocumentStore } } - /// Persist the DRC url an OpenZaak upload (WP-51) registered for a document. + /// Persist the DRC url an OpenZaak upload registered for a document. public static void SetDrcUrl(string documentId, string drcUrl) { lock (_gate) @@ -181,7 +181,7 @@ public static class DocumentStore } /// Append one metadata-only audit row. must arrive - /// **already redacted** (RB-04/BIO-005) — the two citizen call sites pass + /// **already redacted** (BIO-005) — the two citizen call sites pass /// of the owner BSN, `delete-admin` passes the literal /// `"admin"`. The unmasked BSN lives only in , which is /// the authorization key and stays untouched. Masking here instead would have to guess diff --git a/backend/src/BigRegister.Api/Data/FeatureFlagStore.cs b/backend/src/BigRegister.Api/Data/FeatureFlagStore.cs index b016d19..8ca87d8 100644 --- a/backend/src/BigRegister.Api/Data/FeatureFlagStore.cs +++ b/backend/src/BigRegister.Api/Data/FeatureFlagStore.cs @@ -14,7 +14,7 @@ public sealed class FeatureFlagEntity public sealed record ResolvedFlag(string Key, string Description, bool Enabled); /// -/// Runtime feature-flag state (WP-47). SQLite-backed like , same +/// Runtime feature-flag state. SQLite-backed like , same /// single-gate idiom. The CATALOG (which flags exist + their defaults) is code /// (); this store only holds the admin's on/off overrides. An unknown /// key is never writable/enabled — the code catalog is the authority. diff --git a/backend/src/BigRegister.Api/Data/IDocumentSource.cs b/backend/src/BigRegister.Api/Data/IDocumentSource.cs index 4c3e827..e4eaece 100644 --- a/backend/src/BigRegister.Api/Data/IDocumentSource.cs +++ b/backend/src/BigRegister.Api/Data/IDocumentSource.cs @@ -4,10 +4,10 @@ using BigRegister.Domain.Authorization; namespace BigRegister.Api.Data; /// -/// The documents seam (WP-51), sibling of : uploads always land +/// The documents seam, sibling of : uploads always land /// locally first ( stays the record of truth for preview/download/ -/// audit regardless of config, exactly like ApplicationStore.Submit for aanvragen, -/// WP-50) — this interface is only the OpenZaak integration side-effect, selected the same way +/// audit regardless of config, exactly like ApplicationStore.Submit for aanvragen) +/// — this interface is only the OpenZaak integration side-effect, selected the same way /// (Zgw:Enabled). Default binding is (offline); /// OpenZaakDocumentSource also registers each upload as a DRC /// enkelvoudiginformatieobject and links it to a zaak once one exists. @@ -16,17 +16,17 @@ public interface IDocumentSource { /// Store an uploaded file (already validated by DocumentRules) and return the /// existing DTO unchanged, whichever source is active. - /// (WP-53) is both the document's owner (DocumentStore's + /// is both the document's owner (DocumentStore's /// ownership field) and, under the OpenZaak source, the identity minted into the ZGW JWT. UploadResponse Upload( string localId, string categoryId, string wizardId, string fileName, string contentType, byte[] content, ZorgverlenerCaller caller); - /// Finalise a set of already-uploaded documents against a just-submitted aanvraag - /// (WP-50/51): local behaviour is exactly today's DocumentStore.Link; the OpenZaak + /// Finalise a set of already-uploaded documents against a just-submitted aanvraag: + /// local behaviour is exactly today's DocumentStore.Link; the OpenZaak /// source additionally links each document (that has a DRC url) to the zaak, once /// is known (null under the local , in /// which case there is nothing extra to link) — minted with 's - /// identity (WP-53). + /// identity. void LinkToZaak(IReadOnlyList documentIds, string? zaakUrl, CallerIdentity caller); } diff --git a/backend/src/BigRegister.Api/Data/IZaakSource.cs b/backend/src/BigRegister.Api/Data/IZaakSource.cs index 71403da..368da08 100644 --- a/backend/src/BigRegister.Api/Data/IZaakSource.cs +++ b/backend/src/BigRegister.Api/Data/IZaakSource.cs @@ -5,25 +5,25 @@ using BigRegister.Domain.Authorization; namespace BigRegister.Api.Data; /// -/// The cases (zaken) READ seam (WP-49). A "zaak" in ZGW terms is an +/// The cases (zaken) READ seam. A "zaak" in ZGW terms is an /// here; this interface is the one injection point that lets a real ZGW backend (OpenZaak) /// replace the local SQLite store behind the same /// contract — so the frontend never changes (BFF-lite anti-corruption, ADR-0001). /// /// Default binding is (offline). Setting Zgw:Enabled=true -/// swaps in OpenZaakZaakSource. Slice 1 (WP-49) was read-only; -/// (WP-50) is the first write. The interface returns the wire DTO (not the domain +/// swaps in OpenZaakZaakSource. The first slice was read-only; +/// is the first write. The interface returns the wire DTO (not the domain /// ) precisely so each source owns its own mapping — the OpenZaak /// source maps a ZGW Zaak into this shape, the local source maps the stored aanvraag. /// public interface IZaakSource { - /// Every case across every owner, newest-first (the admin cross-owner list, - /// WP-36) — cases:manage only, deliberately NOT citizen-scoped. + /// Every case across every owner, newest-first (the admin cross-owner list) — + /// cases:manage only, deliberately NOT citizen-scoped. IReadOnlyList ListCases(DateTimeOffset now); /// - /// Only 's own cases (WP-53) — the citizen-scoped counterpart of + /// Only 's own cases — the citizen-scoped counterpart of /// , backing the citizen's own dashboard. The local source filters /// ApplicationStore by owner (unchanged behaviour); the OpenZaak source adds ZGW's /// rol__betrokkeneIdentificatie__natuurlijkPersoon__inpBsn query filter so a citizen @@ -32,9 +32,9 @@ public interface IZaakSource IReadOnlyList ListMyCases(ZorgverlenerCaller caller, DateTimeOffset now); /// - /// Register a just-submitted as a zaak (WP-50). The aanvraag is + /// Register a just-submitted as a zaak. The aanvraag is /// already persisted locally (ApplicationStore.Submit already ran, hence the - /// parameter type — WP-73: a freshly submitted aanvraag + /// parameter type — a freshly submitted aanvraag /// always has a Referentie, so neither implementation needs a null-forgiving deref for it /// any more) — this is the integration side-effect, and (Referentie, Status) is what the /// submit endpoint hands back to the FE (ADR-0001: route the create through the existing @@ -42,19 +42,19 @@ public interface IZaakSource /// the already-computed local reference/status (ZaakUrl null — nothing to persist); the /// OpenZaak source creates a Zaak (+ status + rol) and maps the result back into the same /// shape, returning the zaak's URL so the endpoint can persist it - /// (, WP-51 needs it to later link documents to this - /// zaak). (WP-53) is the acting citizen — the ZGW JWT's audit + /// (; linking documents to this + /// zaak later needs it). is the acting citizen — the ZGW JWT's audit /// claims reflect them, not a static config identity. /// (string Referentie, AanvraagStatusDto Status, string? ZaakUrl) CreateZaak(Aanvraag.Submitted aanvraag, DateTimeOffset now, CallerIdentity caller); /// - /// Extend a behandelaar's already-locally-recorded decision (WP-65b's - /// ApplicationStore.RecordBesluit already ran) with a ZGW-side status transition - /// (WP-66) — the write counterpart to 's initial status. The local + /// Extend a behandelaar's already-locally-recorded decision ( + /// ApplicationStore.RecordBesluit already ran) with a ZGW-side status transition — + /// the write counterpart to 's initial status. The local /// source is a no-op (the decision IS the record of truth there, unchanged from before this /// seam existed); the OpenZaak source POSTs a new Statussen entry to - /// 's zaak. (WP-53/62) is the acting + /// 's zaak. is the acting /// medewerker. /// void RecordBesluit(Aanvraag aanvraag, Besluit besluit, string? toelichting, DateTimeOffset now, CallerIdentity caller); diff --git a/backend/src/BigRegister.Api/Data/LocalDocumentSource.cs b/backend/src/BigRegister.Api/Data/LocalDocumentSource.cs index 6b47384..d00da40 100644 --- a/backend/src/BigRegister.Api/Data/LocalDocumentSource.cs +++ b/backend/src/BigRegister.Api/Data/LocalDocumentSource.cs @@ -5,7 +5,7 @@ namespace BigRegister.Api.Data; /// /// The default — uploads go only to the local SQLite -/// , exactly as before this seam existed (WP-51). Zero behaviour +/// , exactly as before this seam existed. Zero behaviour /// change: this is the same DocumentStore.Add/DocumentStore.Link the upload/ /// submit endpoints used to call inline. /// diff --git a/backend/src/BigRegister.Api/Data/LocalZaakSource.cs b/backend/src/BigRegister.Api/Data/LocalZaakSource.cs index 13f59a0..ab130eb 100644 --- a/backend/src/BigRegister.Api/Data/LocalZaakSource.cs +++ b/backend/src/BigRegister.Api/Data/LocalZaakSource.cs @@ -6,7 +6,7 @@ namespace BigRegister.Api.Data; /// /// The default — the cases come from the local SQLite -/// , exactly as before the seam existed (WP-49). Zero +/// , exactly as before the seam existed. Zero /// behaviour change: this is the same ListAll().ToAdminSummaryDto(now) the /// /admin/cases endpoint used to call inline. /// @@ -15,7 +15,7 @@ public sealed class LocalZaakSource : IZaakSource public IReadOnlyList ListCases(DateTimeOffset now) => ApplicationStore.ListAll().Select(a => a.ToAdminSummaryDto(now)).ToList(); - /// Citizen-scoped (WP-53) — exactly what GET /aanvragen used to compute + /// Citizen-scoped — exactly what GET /aanvragen used to compute /// inline before it was routed through this seam. public IReadOnlyList ListMyCases(ZorgverlenerCaller caller, DateTimeOffset now) => ApplicationStore.List(caller.Bsn) @@ -23,11 +23,11 @@ public sealed class LocalZaakSource : IZaakSource .Select(a => a.ToSummaryDto(now)).ToList(); /// No external zaak to create — the aanvraag's local submit already IS the record - /// of truth, exactly as before this seam existed (WP-50). Zero behaviour change. + /// of truth, exactly as before this seam existed. Zero behaviour change. public (string Referentie, AanvraagStatusDto Status, string? ZaakUrl) CreateZaak(Aanvraag.Submitted aanvraag, DateTimeOffset now, CallerIdentity caller) => (aanvraag.Referentie, aanvraag.ToStatusDto(now), null); /// No external zaak to update — the recorded decision already IS the record of - /// truth locally (WP-66). Zero behaviour change. + /// truth locally. Zero behaviour change. public void RecordBesluit(Aanvraag aanvraag, Besluit besluit, string? toelichting, DateTimeOffset now, CallerIdentity caller) { } } diff --git a/backend/src/BigRegister.Api/Data/OrgTemplateStore.cs b/backend/src/BigRegister.Api/Data/OrgTemplateStore.cs index a6b3f3d..c163672 100644 --- a/backend/src/BigRegister.Api/Data/OrgTemplateStore.cs +++ b/backend/src/BigRegister.Api/Data/OrgTemplateStore.cs @@ -3,12 +3,12 @@ using BigRegister.Api.Contracts; namespace BigRegister.Api.Data; /// -/// Organization template per sub-organization (WP-23, Brief v2 PRD §3): one row per +/// Organization template per sub-organization (Brief v2 PRD §3): one row per /// sub-org. `Draft` is the work-in-progress payload (Version 0), `History` the /// append-only list of published snapshots, `PublishedVersion` points into it. /// Rollback copies an old snapshot back into the draft — it never rewrites history. /// Mirrors : static class, short-lived context per call, -/// nested DTO shapes stored as JSON text columns (WP-22 posture). +/// nested DTO shapes stored as JSON text columns. /// public sealed class OrgTemplateEntity { diff --git a/backend/src/BigRegister.Api/Domain/Applications/Aanvraag.cs b/backend/src/BigRegister.Api/Domain/Applications/Aanvraag.cs index 4954b64..4dff4c5 100644 --- a/backend/src/BigRegister.Api/Domain/Applications/Aanvraag.cs +++ b/backend/src/BigRegister.Api/Domain/Applications/Aanvraag.cs @@ -3,7 +3,7 @@ using System.Text.Json; namespace BigRegister.Domain.Applications; /// -/// The aanvraag lifecycle as a closed union (WP-73): (the pre-submission +/// The aanvraag lifecycle as a closed union: (the pre-submission /// wizard draft) → (awaiting a behandelaar's decision, or already /// auto-rejected at submission time — see ) → /// (a behandelaar's outcome recorded). Each variant carries only the fields that make sense for @@ -31,11 +31,11 @@ public abstract record Aanvraag public required DateTimeOffset CreatedAt { get; init; } public required DateTimeOffset UpdatedAt { get; init; } - /// The OpenZaak zaak's URL, set once CreateZaak (WP-50) registers one — null under + /// The OpenZaak zaak's URL, set once CreateZaak registers one — null under /// the local source, or before a zaak has been registered at all. public string? ZaakUrl { get; init; } - /// WP-60: non-null means the ZGW side of this aanvraag's last write did not + /// Non-null means the ZGW side of this aanvraag's last write did not /// complete — see Api.Data.ApplicationStore.SetZgwError. public string? ZgwError { get; init; } @@ -76,7 +76,7 @@ public abstract record Aanvraag public string? Reden { get; init; } } - /// A behandelaar's decision (WP-65b/68) — closed by besluit: only + /// A behandelaar's decision — closed by besluit: only /// / require a toelichting /// (BeoordelingRules.RequiresToelichting's rule, now also a type, not just an endpoint /// check) — omitting it is a compile error, not merely a 400 the type happens to also let diff --git a/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs b/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs index 7af6919..ed0c9c3 100644 --- a/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs +++ b/backend/src/BigRegister.Api/Domain/Applications/AanvraagStatus.cs @@ -1,11 +1,11 @@ namespace BigRegister.Domain.Applications; /// -/// The post-submission aanvraag status lifecycle (ADR-0002, WP-63): Ingediend → In +/// The post-submission aanvraag status lifecycle (ADR-0002): Ingediend → In /// behandeling → (Meer info gevraagd ⇄) → Goedgekeurd/Afgewezen. Concept (pre-submission, /// the wizard draft) is deliberately NOT a member here — see , /// which is null exactly when the aanvraag hasn't been submitted yet, instead of a sixth -/// "magic string" tag with no enum member to match it (WP-68 F3). +/// "magic string" tag with no enum member to match it. /// is reserved: no endpoint sets it yet (there is no state between /// "just submitted" and "in behandeling" in this POC) — kept because the FE's status union /// and $localize catalogue already declare it, and removing it would ripple into both. @@ -13,13 +13,13 @@ namespace BigRegister.Domain.Applications; public enum AanvraagStatusTag { Ingediend, InBehandeling, MeerInfoGevraagd, Goedgekeurd, Afgewezen } /// -/// A behandelaar's recorded decision (WP-65b) — the three actions the beoordeling screen +/// A behandelaar's recorded decision — the three actions the beoordeling screen /// offers, each advancing an aanvraag's . /// public enum Besluit { Goedkeuren, Afwijzen, MeerInfoOpvragen } /// -/// The domain projection of an aanvraag's status at a point in time (WP-68 F3) — the type +/// The domain projection of an aanvraag's status at a point in time — the type /// Aanvraag.StatusAt(now) returns, replacing the logic that used to live directly in /// Contracts.Mappers.ToStatusDto. Constructible only via the factories below, so a /// caller can never build e.g. a Referentie-less Goedgekeurd. is null only diff --git a/backend/src/BigRegister.Api/Domain/Authorization/Authz.cs b/backend/src/BigRegister.Api/Domain/Authorization/Authz.cs index 9410a6d..d961b94 100644 --- a/backend/src/BigRegister.Api/Domain/Authorization/Authz.cs +++ b/backend/src/BigRegister.Api/Domain/Authorization/Authz.cs @@ -24,7 +24,7 @@ public enum BriefAction { Approve, Reject, Send } /// public static class Authz { - // WP-53: role now comes from the per-request CallerIdentity the identity middleware + // Role now comes from the per-request CallerIdentity the identity middleware // resolved (StubIdentityProvider reads the same X-Role header this used to read directly) — // one source of "who", so a real IIdentityProvider swap carries this over unchanged. public static Principal ResolvePrincipal(HttpContext ctx) => new(ctx.Caller().Role); @@ -49,7 +49,7 @@ public static class Authz /// BriefStore.Review enforces before its status guard; kept separate from /// Decisions() below so enforcement ORDER (Forbidden before Conflict) matches /// today's behavior exactly. The explicit Approver condition keeps the new Admin - /// role out of the review flow (WP-23) — SoD alone would have let it through. + /// role out of the review flow — SoD alone would have let it through. public static bool CanActOn(BriefAction action, Principal principal, string drafterId) => action switch { BriefAction.Approve or BriefAction.Reject => @@ -58,7 +58,7 @@ public static class Authz _ => false, }; - /// Org-template management (WP-23): admin-only, resource-independent — templates + /// Org-template management: admin-only, resource-independent — templates /// have no per-resource state to weigh, so role IS the whole decision here. public static bool CanManageOrgTemplates(Principal principal) => principal.Role == PrincipalRole.Admin; @@ -67,18 +67,18 @@ public static class Authz /// the maintenance editor consumes; the actual edit lands as a reviewed PR, not a write here. public static bool CanEditStamdata(Principal principal) => principal.Role == PrincipalRole.Admin; - /// Case management (WP-36): admin-only, resource-independent — same shape as + /// Case management: admin-only, resource-independent — same shape as /// org-template / stamdata (role IS the decision). Gates the cross-owner /admin/cases /// list + admin delete. public static bool CanManageCases(Principal principal) => principal.Role == PrincipalRole.Admin; - /// Feature-flag management (WP-47): admin-only, resource-independent — role IS the decision. + /// Feature-flag management: admin-only, resource-independent — role IS the decision. public static bool CanManageFeatureFlags(Principal principal) => principal.Role == PrincipalRole.Admin; - // --- Medewerker (backoffice) capabilities (WP-62, ADR-0002 §3) ------------------------------ + // --- Medewerker (backoffice) capabilities (ADR-0002 §3) ------------------------------ - /// May this caller assess/decide an aanvraag (the behandelportal's werkvoorraad + beoordeling, - /// WP-64/65)? Rol-based, deliberately NOT derived from PrincipalRole — a zorgverlener is false + /// May this caller assess/decide an aanvraag (the behandelportal's werkvoorraad + beoordeling)? + /// Rol-based, deliberately NOT derived from PrincipalRole — a zorgverlener is false /// regardless of X-Role, because the capability belongs to the medewerker actor kind, not to /// the dev role stand-in. Shipped to a frontend only as a decision flag, never as a rollen /// matrix (ADR-0001). diff --git a/backend/src/BigRegister.Api/Domain/Authorization/CallerIdentity.cs b/backend/src/BigRegister.Api/Domain/Authorization/CallerIdentity.cs index 4fdd775..792fc49 100644 --- a/backend/src/BigRegister.Api/Domain/Authorization/CallerIdentity.cs +++ b/backend/src/BigRegister.Api/Domain/Authorization/CallerIdentity.cs @@ -1,8 +1,8 @@ namespace BigRegister.Domain.Authorization; /// -/// The two actor kinds a request can come from (WP-62, ADR-0002 §3): a -/// (citizen, WP-53 — subject BSN) or a (backoffice employee — no BSN, +/// The two actor kinds a request can come from (ADR-0002 §3): a +/// (citizen — subject BSN) or a (backoffice employee — no BSN, /// has rollen). Resolved once per request by and stashed on /// by the identity-resolution middleware (Program.cs, right /// after the correlation-id middleware). Everything that used to hardcode DocumentStore.DemoOwner @@ -33,7 +33,7 @@ public sealed record MedewerkerCaller( /// Backoffice functions a medewerker holds (ADR-0002 §4: admin/auditor/institution-rep /// slot in here as extra rollen, never as new CallerIdentity variants). Deliberately one member — -/// WP-65 adds the next one when a capability actually needs it. +/// A later capability adds the next one when it actually needs it. public enum MedewerkerRol { Behandelaar } public static class CallerIdentityHttpContextExtensions @@ -51,10 +51,10 @@ public static class CallerIdentityHttpContextExtensions : throw new InvalidOperationException( "No CallerIdentity resolved for this request — the identity middleware didn't run."); - /// The citizen-scoped narrowing (WP-62): every SSP endpoint that scopes data by owner + /// The citizen-scoped narrowing: every SSP endpoint that scopes data by owner /// needs a BSN, which only a zorgverlener has. Throws rather than silently degrading — no - /// medewerker reaches these endpoints today (the behandelportal calls its own endpoints, - /// WP-64+), so this is a loud "wrong actor kind" bug detector, not a user-facing path. + /// medewerker reaches these endpoints today (the behandelportal calls its own endpoints), + /// so this is a loud "wrong actor kind" bug detector, not a user-facing path. public static ZorgverlenerCaller Zorgverlener(this HttpContext ctx) => ctx.Caller() as ZorgverlenerCaller ?? throw new InvalidOperationException( diff --git a/backend/src/BigRegister.Api/Domain/Authorization/IIdentityProvider.cs b/backend/src/BigRegister.Api/Domain/Authorization/IIdentityProvider.cs index a69a16d..be16e69 100644 --- a/backend/src/BigRegister.Api/Domain/Authorization/IIdentityProvider.cs +++ b/backend/src/BigRegister.Api/Domain/Authorization/IIdentityProvider.cs @@ -1,11 +1,11 @@ namespace BigRegister.Domain.Authorization; /// -/// Resolves the acting for a request (WP-53) — one of the two actor -/// kinds (WP-62, ADR-0002 §3): a zorgverlener (real DigiD claims in production) or a medewerker +/// Resolves the acting for a request — one of the two actor +/// kinds (ADR-0002 §3): a zorgverlener (real DigiD claims in production) or a medewerker /// (real employee SSO/eHerkenning claims in production). is /// the only implementation today, and is registered only in Development (Program.cs, -/// RB-09/BIO-002). +/// BIO-002). /// public interface IIdentityProvider { diff --git a/backend/src/BigRegister.Api/Domain/Authorization/StubIdentityProvider.cs b/backend/src/BigRegister.Api/Domain/Authorization/StubIdentityProvider.cs index 0a721cd..edce9f1 100644 --- a/backend/src/BigRegister.Api/Domain/Authorization/StubIdentityProvider.cs +++ b/backend/src/BigRegister.Api/Domain/Authorization/StubIdentityProvider.cs @@ -3,18 +3,18 @@ using BigRegister.Api.Data; namespace BigRegister.Domain.Authorization; /// -/// Dev stub (WP-53, extended WP-62) — NOT a security boundary, same caveat as +/// Dev stub — NOT a security boundary, same caveat as /// (which this provider now backs). Role comes from the /// existing client-asserted X-Role header (mirrors the FE's ?role= toggle) and applies to /// either actor kind. Presence of X-Medewerker selects a (id + /// rollen from X-Rollen) and takes precedence over X-Subject; absent — every request today — -/// falls through to the WP-53 path unchanged: subject BSN from +/// falls through to the path unchanged: subject BSN from /// X-Subject, defaulting to the single seeded citizen (). /// A real system builds this from verified DigiD claims (zorgverlener) / employee SSO claims /// (medewerker); every consumer of carries over unchanged once that /// swap happens. /// -/// Registered only in Development (Program.cs, RB-09/BIO-002) — it always invents a +/// Registered only in Development (Program.cs, BIO-002) — it always invents a /// caller for a request with no credential, which is a deliberate developer convenience, not /// something a production build may do. Its own return type stays non-nullable: unlike /// , this stub never has "no identity" to report. diff --git a/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs b/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs index 9b7e12e..c4b0606 100644 --- a/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs +++ b/backend/src/BigRegister.Api/Domain/Beoordeling/BeoordelingRules.cs @@ -3,10 +3,10 @@ using BigRegister.Domain.Applications; namespace BigRegister.Domain.Beoordeling; /// -/// SERVER-OWNED rules for the behandelportal's case-treatment decision (WP-65). Used from +/// SERVER-OWNED rules for the behandelportal's case-treatment decision. Used from /// both the beoordeling read side ( backs the `canBesluiten` decision -/// flag) and the besluit write side (the SAME `CanDecide` gates the mutation, and — since -/// WP-68 F2 — runs inside the write lock, so the two can never drift and a concurrent besluit +/// flag) and the besluit write side (the SAME `CanDecide` gates the mutation, and +/// runs inside the write lock, so the two can never drift and a concurrent besluit /// can't race past the check). /// public static class BeoordelingRules @@ -18,7 +18,7 @@ public static class BeoordelingRules current is AanvraagStatusTag.Ingediend or AanvraagStatusTag.InBehandeling or AanvraagStatusTag.MeerInfoGevraagd; - /// WP-68 F6: moved here from an inline check in the besluit endpoint. The + /// Moved here from an inline check in the besluit endpoint. The /// toelichting (behandelaar's explanation) is required for every besluit except an /// approval — Afwijzen/MeerInfoOpvragen must justify why (becomes the published status's /// Reden). diff --git a/backend/src/BigRegister.Api/Domain/Documents/DocumentCategory.cs b/backend/src/BigRegister.Api/Domain/Documents/DocumentCategory.cs index 4f47492..4497901 100644 --- a/backend/src/BigRegister.Api/Domain/Documents/DocumentCategory.cs +++ b/backend/src/BigRegister.Api/Domain/Documents/DocumentCategory.cs @@ -43,7 +43,7 @@ public static class DocumentRules new DocumentCategory("nascholing", "Nascholingscertificaten", "Upload uw nascholingscertificaten (optioneel).", false, PdfImage, 10, true, true), }, - // WP-23: the admin's org-template logo rides the same upload machinery as the + // The admin's org-template logo rides the same upload machinery as the // wizard documents — one category under its own "wizard" id. "org-template" => new[] { diff --git a/backend/src/BigRegister.Api/Domain/Intake/IntakePolicy.cs b/backend/src/BigRegister.Api/Domain/Intake/IntakePolicy.cs index c058af9..bb31626 100644 --- a/backend/src/BigRegister.Api/Domain/Intake/IntakePolicy.cs +++ b/backend/src/BigRegister.Api/Domain/Intake/IntakePolicy.cs @@ -5,7 +5,7 @@ namespace BigRegister.Domain.Intake; /// scholing question is required. The frontend receives this value /// (GET /intake/policy) and applies it for instant UX feedback /// (intake.machine.ts's lageUren); is the -/// backend re-validating it as the authority on submit (WP-69) — +/// backend re-validating it as the authority on submit — /// POST /aanvragen/{id}/submit (intake-typed aanvragen only) calls it before /// writing anything, and a violation 400s (ProblemDetails), never silently accepts /// an incomplete answer. @@ -15,10 +15,10 @@ public static class IntakePolicy public const int ScholingThreshold = 1000; /// - /// Completeness rule for the scholing question (WP-69) — not merit: below + /// Completeness rule for the scholing question — not merit: below /// an answer must be present, but "nee" is a legal answer - /// that still submits (turning "few uren + no scholing" into a rejection is out of scope, - /// see the WP). Three-valued, so two parameters (uren, punten) couldn't express it: + /// that still submits (turning "few uren + no scholing" into a rejection is out of scope). + /// Three-valued, so two parameters (uren, punten) couldn't express it: /// /// below threshold and no answer at all ⇒ incomplete; /// answered true (scholing gevolgd) ⇒ punten required and non-negative diff --git a/backend/src/BigRegister.Api/Domain/Letters/BriefRules.cs b/backend/src/BigRegister.Api/Domain/Letters/BriefRules.cs index a5bea7c..644929d 100644 --- a/backend/src/BigRegister.Api/Domain/Letters/BriefRules.cs +++ b/backend/src/BigRegister.Api/Domain/Letters/BriefRules.cs @@ -5,7 +5,7 @@ using BigRegister.Domain.Authorization; namespace BigRegister.Domain.Letters; /// -/// SERVER-OWNED brief state-transition and authorization rules (RB-30, TE-008). Each +/// SERVER-OWNED brief state-transition and authorization rules (TE-008). Each /// method is a pure decision over (status tag, actor role, entity completeness) — /// extracted out of 's lock-held, DB-opening methods so the /// decision can be unit-tested without a booted host or a real SQLite file. Callers diff --git a/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs b/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs index 8ddc879..92d2ccd 100644 --- a/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs +++ b/backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs @@ -6,7 +6,7 @@ using BigRegister.Api.Data; namespace BigRegister.Domain.Letters; /// -/// Server-rendered letter HTML (WP-25) — the archived, "what is sent" artifact. +/// Server-rendered letter HTML — the archived, "what is sent" artifact. /// Mirrors the FE letter canvas' class vocabulary exactly (public/letter.css, /// the FE⇄BE contract; LetterHtmlTests' class-parity test is the fence against drift). /// @@ -151,7 +151,8 @@ public static class LetterHtml private static string EncLines(string s) => Enc(s).Replace("\n", "
"); // Walks up from the running assembly's own directory (NOT the process cwd, which - // varies by how `dotnet run`/docker/tests invoke it — see docs/project/backlog/WP-25) until + // varies by how `dotnet run`/docker/tests invoke it — see the letter-preview-html design + // note in docs/project/backlog/) until // it finds `public/letter.css`. docker-compose.yml bind-mounts `./public` under the // api container's `/src` for exactly this walk to resolve there too. private static string FindLetterCss() diff --git a/backend/src/BigRegister.Api/Domain/Registrations/Registration.cs b/backend/src/BigRegister.Api/Domain/Registrations/Registration.cs index b3d0bad..c2b4350 100644 --- a/backend/src/BigRegister.Api/Domain/Registrations/Registration.cs +++ b/backend/src/BigRegister.Api/Domain/Registrations/Registration.cs @@ -9,8 +9,8 @@ public enum StatusTag } /// -/// Status as a closed union: each variant carries exactly the data that makes sense for it -/// (WP-73). Only carries a herregistratie deadline; only +/// Status as a closed union: each variant carries exactly the data that makes sense for it. +/// Only carries a herregistratie deadline; only /// and carry a reden — and there it is /// required, not nullable (the old flat record left Reden nullable on every tag, /// diverging from the frontend union, which has always required it on those two variants — diff --git a/backend/src/BigRegister.Api/Domain/Submissions/SubmissionRules.cs b/backend/src/BigRegister.Api/Domain/Submissions/SubmissionRules.cs index c9ee980..1b85ff7 100644 --- a/backend/src/BigRegister.Api/Domain/Submissions/SubmissionRules.cs +++ b/backend/src/BigRegister.Api/Domain/Submissions/SubmissionRules.cs @@ -22,9 +22,9 @@ public static class SubmissionRules // RULE: a contact change needs a well-formed Dutch phone number (10 digits, leading // 0, formatting stripped). The BRP address is authoritative and cannot be changed - // here (WP-34), so only the phone is submitted. The server re-validates format + // here, so only the phone is submitted. The server re-validates format // authoritatively (the FE check is UX-only) — and must strip the SAME formatting the - // FE's parseTelefoonnummer does (whitespace/dashes/parens, a leading +31 → 0; WP-75), + // FE's parseTelefoonnummer does (whitespace/dashes/parens, a leading +31 → 0), // or the two sides disagree on what's a valid number. public static string? RejectPhoneChange(string telefoon) { diff --git a/backend/src/BigRegister.Api/Program.cs b/backend/src/BigRegister.Api/Program.cs index b59f6ff..97d1ca7 100644 --- a/backend/src/BigRegister.Api/Program.cs +++ b/backend/src/BigRegister.Api/Program.cs @@ -39,20 +39,20 @@ const string SpaCors = "spa"; builder.Services.AddCors(o => o.AddPolicy(SpaCors, p => p.WithOrigins("http://localhost:4200").AllowAnyHeader().AllowAnyMethod())); -// WP-22: the three stores (Applications/Documents/Briefs — Data/*.cs) are static +// The three stores (Applications/Documents/Briefs — Data/*.cs) are static // classes that open their own short-lived AppDbContext per call (see Db.Create), // not DI-injected, so there's no builder.Services.AddDbContext here. Configuring // the connection string still goes through IConfiguration so tests/deployments can // override it (ConnectionStrings:AppDb) without touching this file. Db.ConnectionString = builder.Configuration.GetConnectionString("AppDb") ?? Db.ConnectionString; -// WP-53 (extended WP-62): the per-request acting caller — resolved once (middleware, below) +// The per-request acting caller — resolved once (middleware, below) // into HttpContext.Items, consumed by Authz.ResolvePrincipal, ZgwTokenProvider.Mint(caller), and // every store call site that used to hardcode DocumentStore.DemoOwner. Stub today (X-Role/ // X-Subject for a zorgverlener, X-Medewerker/X-Rollen for a medewerker); a real // DigiD/employee-SSO provider swaps in without touching a consumer. // -// RB-09/BIO-002: StubIdentityProvider invents a citizen identity for any request with no +// BIO-002: StubIdentityProvider invents a citizen identity for any request with no // credential at all — a production behandelportal build sends no X-Medewerker header, so it // used to authenticate every request as the seeded citizen (open on that citizen's own rights, // including CanRevealBigNummer). Registering the stub only in Development, and failing to @@ -65,10 +65,10 @@ if (builder.Environment.IsDevelopment()) else if (builder.Environment.IsProduction()) throw new InvalidOperationException( "No IIdentityProvider is registered for a Production environment. StubIdentityProvider " + - "is Development-only (RB-09/BIO-002); there is no real DigiD/employee-SSO provider in " + + "is Development-only (BIO-002); there is no real DigiD/employee-SSO provider in " + "this POC yet. Register one before deploying to Production."); -// WP-49: the cases (zaken) READ path goes through IZaakSource so a real ZGW backend +// The cases (zaken) READ path goes through IZaakSource so a real ZGW backend // (OpenZaak) can replace the local SQLite store behind the same DTO contract — the FE never // changes (ADR-0001). Default = LocalZaakSource (offline). Zgw:Enabled=true swaps in the // OpenZaak client (needs the base URLs + credentials in the Zgw config section). @@ -77,11 +77,11 @@ if (zgw.Enabled) { builder.Services.AddSingleton(zgw); builder.Services.AddSingleton(); - // WP-60: a bounded client timeout matters once ZgwHttpClient retries — without one, the + // A bounded client timeout matters once ZgwHttpClient retries — without one, the // sources' sync-over-async call (no CancellationToken threaded through) could block a // thread-pool thread for HttpClient's 100s default times 3 attempts. var zaakClientBuilder = builder.Services.AddHttpClient(c => c.Timeout = TimeSpan.FromSeconds(15)); - // WP-51: the documents (Documenten API / DRC) seam — same pattern as IZaakSource above. + // The documents (Documenten API / DRC) seam — same pattern as IZaakSource above. var documentClientBuilder = builder.Services.AddHttpClient(c => c.Timeout = TimeSpan.FromSeconds(15)); // Opt-in diagnostic for the still-unexplained per-container flake (see @@ -101,10 +101,10 @@ else var app = builder.Build(); -// Migrate on every startup, seed nothing (WP-22): unlike SeedData's read-only -// reference fixtures (registration/diplomas/notes — untouched by this WP, still +// Migrate on every startup, seed nothing: unlike SeedData's read-only +// reference fixtures (registration/diplomas/notes — untouched by this change, still // static in-memory), Applications/Documents/Briefs never had seed data — they -// started empty and accumulated through normal use before this WP too. A fresh +// started empty and accumulated through normal use before this change too. A fresh // SQLite file just starts empty again, same as the old in-memory dictionaries did. using (var db = Db.Create()) db.Database.Migrate(); @@ -124,9 +124,9 @@ app.Use(async (ctx, next) => await next(ctx); }); -// WP-53: resolve the acting citizen once per request, right after correlation — everything +// Resolve the acting citizen once per request, right after correlation — everything // downstream (Authz.ResolvePrincipal, the endpoints below) reads it via ctx.Caller() instead of -// re-deriving "who" itself. RB-09/BIO-002: a null resolution is "no identity", not "the seeded +// re-deriving "who" itself. BIO-002: a null resolution is "no identity", not "the seeded // citizen" — this is the one place that turns it into a response (401) rather than letting it // flow downstream as a silent identity substitution. var identityProvider = app.Services.GetRequiredService(); @@ -142,7 +142,7 @@ app.Use(async (ctx, next) => await next(ctx); }); -// RB-15/BIO-015: the OpenAPI document + its UI are a genuine attack-surface reduction to +// BIO-015: the OpenAPI document + its UI are a genuine attack-surface reduction to // gate — they enumerate every route, request/response shape and (via SwaggerUI's "Try it // out") let a caller fire requests straight from the browser. Development-only, like the // dev-role/scenario-toggle hatches this POC already keeps out of production builds @@ -150,8 +150,8 @@ app.Use(async (ctx, next) => // Development). `dotnet swagger tofile` (npm run gen:api) is unaffected: Swashbuckle's CLI // resolves ISwaggerProvider straight out of the DI container to build swagger.json — it // never sends an HTTP request through this pipeline, so it never touches this middleware at -// all, gated or not. Verified empirically (see rb-15.md) rather than assumed, per RB-09's -// note that this exact file has already broken that tool once. +// all, gated or not. Verified empirically (see rb-15.md) rather than assumed — a past +// regression already broke that tool once in this exact file. if (app.Environment.IsDevelopment()) { app.UseSwagger(); @@ -213,7 +213,7 @@ api.MapGet("/stamdata/{table}", (string table, string? peildatum, HttpContext ct var t = StamdataCatalog.Find(table); if (t is null) return Results.NotFound(); DateOnly? peildatumWaarde = null; - // RB-16/BIO-019: DateOnly.Parse threw FormatException on unparseable input, surfacing as + // BIO-019: DateOnly.Parse threw FormatException on unparseable input, surfacing as // an unhandled 500 (and, in Development, an exception detail leaked to the caller) — an // admin-gated but still user-supplied string needs the same 400 path every other bad-input // check in this file uses, not a crash. @@ -250,7 +250,7 @@ api.MapGet("/uploads/categories", (string wizardId, string? diplomaHerkomst, str // Serve stored bytes so a re-opened wizard can preview/download an upload. Inline // for pdf/image (browser renders it), attachment otherwise (download). -// Scoped like DELETE on the same resource (RB-01/BIO-004): the owning citizen, or a +// Scoped like DELETE on the same resource (BIO-004): the owning citizen, or a // behandelaar reading an aanvraag's linked documents. A foreign id 404s rather than // 403s, so the endpoint never confirms that a document id exists. api.MapGet("/uploads/{documentId}/content", (string documentId, HttpContext ctx) => @@ -272,7 +272,7 @@ api.MapGet("/uploads/{documentId}/content", (string documentId, HttpContext ctx) api.MapGet("/uploads/status", (string? localIds, HttpContext ctx) => { var ids = (localIds ?? "").Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - // Owner-scoped (RB-01/BIO-004): someone else's localId reads back as "unknown", the + // Owner-scoped (BIO-004): someone else's localId reads back as "unknown", the // same answer an id that never existed gets. var found = DocumentStore.ByLocalIds(ids, ctx.Zorgverlener().Bsn).ToDictionary(d => d.LocalId); var results = ids.Select(id => found.TryGetValue(id, out var d) @@ -301,7 +301,7 @@ api.MapPost("/uploads", async (HttpRequest request, HttpContext ctx, IDocumentSo using var ms = new MemoryStream(); await file.CopyToAsync(ms); - // WP-51: route through IDocumentSource — LocalDocumentSource is the same DocumentStore.Add + // Route through IDocumentSource — LocalDocumentSource is the same DocumentStore.Add // call this used to make inline; OpenZaakDocumentSource (Zgw:Enabled=true) also registers // the file as a DRC enkelvoudiginformatieobject. Response DTO unchanged either way. var response = documents.Upload(localId, categoryId, wizardId, file.FileName, file.ContentType, ms.ToArray(), ctx.Zorgverlener()); @@ -325,8 +325,8 @@ api.MapDelete("/uploads/{documentId}", (string documentId, HttpContext ctx) => // Admin delete: bypasses ownership, unlinks, and flags the submission for review. Gated // by the same CasesAdmin wrapper (cases:manage) the other admin-cases endpoints use -// (RB-08/BIO-003) — it used to be gated by a standalone X-Admin header, outside Authz and -// unaudited; CasesAdmin gives it the missing AuthzAuditStore row for free (RB-07). +// (BIO-003) — it used to be gated by a standalone X-Admin header, outside Authz and +// unaudited; CasesAdmin gives it the missing AuthzAuditStore row for free. api.MapDelete("/admin/uploads/{documentId}", (string documentId, HttpContext ctx) => CasesAdmin(ctx, () => DocumentStore.AdminDelete(documentId, "admin") ? Results.NoContent() : Results.NotFound())) .Gate("CasesAdmin") @@ -338,7 +338,7 @@ api.MapDelete("/admin/uploads/{documentId}", (string documentId, HttpContext ctx // --- reads --- -// WP-53: routed through IZaakSource (like /admin/cases already was) rather than calling +// Routed through IZaakSource (like /admin/cases already was) rather than calling // ApplicationStore directly — under Zgw:Enabled=true a citizen's own dashboard list comes from // OpenZaak (BSN-filtered) too, closing the last "reads a static store directly" gap // openzaak-integration.md's ACL caveat used to flag for this endpoint. @@ -356,7 +356,7 @@ api.MapGet("/aanvragen/{id}", (string id, HttpContext ctx) => api.MapPost("/aanvragen", (CreateAanvraagRequest req, HttpContext ctx) => { - // Feature flag (WP-47): self-service registration can be closed by an admin. + // Feature flag: self-service registration can be closed by an admin. if (req.Type == "registratie" && !FeatureFlagStore.IsEnabled(FeatureFlags.InschrijvingOpen)) return Results.Problem(detail: "Inschrijving is momenteel gesloten.", statusCode: StatusCodes.Status403Forbidden); var a = ApplicationStore.CreateConcept(req.Type, ctx.Zorgverlener().Bsn); @@ -417,7 +417,7 @@ api.MapPost("/aanvragen/{id}/submit", (string id, AanvraagIndienenRequest req, H _ /* herregistratie | intake */ => (SubmissionRules.RejectZeroUren(req.Uren ?? 0), true), }; - // WP-69: intake-only (herregistratie has no scholing question) — guarded by `reject is + // Intake-only (herregistratie has no scholing question) — guarded by `reject is // null` so a { uren: 0 } submission is still decided on merit (RejectZeroUren) and // completeness is moot; placed before the document-ownership check and // ApplicationStore.Submit so a rejected submit leaves the aanvraag a Concept (retryable). @@ -442,13 +442,13 @@ api.MapPost("/aanvragen/{id}/submit", (string id, AanvraagIndienenRequest req, H "aanvraag submit id={Id} type={Type} outcome={Outcome} auto={Auto} reference={Reference}", id, existing.Type, reject is null ? "accepted" : "rejected", autoApprovable, submitted.Referentie); - // WP-50: route the create through the IZaakSource seam — LocalZaakSource is a passthrough + // Route the create through the IZaakSource seam — LocalZaakSource is a passthrough // of what was computed above; OpenZaakZaakSource (Zgw:Enabled=true) also registers a zaak // in OpenZaak and maps its result back into this same response shape (ADR-0001/ADR-0005: - // zero FE contract change either way). WP-53: the caller is threaded through so the minted + // zero FE contract change either way). The caller is threaded through so the minted // ZGW JWT's user_id/user_representation reflect the acting citizen, not a static config value. // - // WP-60: the local submit above already committed — it is never rolled back on a ZGW + // The local submit above already committed — it is never rolled back on a ZGW // failure (an orphan zaak from a rolled-back-then-retried submit is worse than a flagged // one, see openzaak-integration.md's "Write resilience" section). Each ZGW half is caught // separately so a create-zaak failure doesn't also skip the (still-local) document link. @@ -465,7 +465,7 @@ api.MapPost("/aanvragen/{id}/submit", (string id, AanvraagIndienenRequest req, H RecordZgwDivergence(ctx, id, referentie, ex); } - // WP-51: link the submitted documents to the zaak — LocalDocumentSource is exactly the + // Link the submitted documents to the zaak — LocalDocumentSource is exactly the // DocumentStore.Link call this used to make inline; OpenZaakDocumentSource additionally // POSTs a zaakinformatieobject per document, now that the zaak (zaakUrl) exists. if (documentIds is not null) @@ -487,7 +487,7 @@ api.MapPost("/aanvragen/{id}/submit", (string id, AanvraagIndienenRequest req, H .ProducesProblem(StatusCodes.Status409Conflict) .Produces(StatusCodes.Status404NotFound); -// --- Admin cases (WP-36): cross-owner list + admin delete, gated by `cases:manage`. --- +// --- Admin cases: cross-owner list + admin delete, gated by `cases:manage`. --- // --- reads --- @@ -497,7 +497,7 @@ api.MapGet("/admin/cases", (HttpContext ctx, IZaakSource zaken) => CasesAdmin(ct .Produces>() .ProducesProblem(StatusCodes.Status403Forbidden); -// Queryable authz/PII-reveal audit trail (WP-41) — data-minimised, no PII. Admin-gated +// Queryable authz/PII-reveal audit trail — data-minimised, no PII. Admin-gated // via the existing CasesAdmin (cases:manage); a dedicated audit:read cap is a later refinement. api.MapGet("/admin/audit", (HttpContext ctx) => CasesAdmin(ctx, () => Results.Ok(AuthzAuditStore.List() @@ -522,9 +522,9 @@ api.MapDelete("/admin/cases/{id}", (string id, HttpContext ctx) => CasesAdmin(ct .Produces(StatusCodes.Status404NotFound) .ProducesProblem(StatusCodes.Status403Forbidden); -// --- Werkvoorraad (WP-64): the behandelportal's queue of aanvragen needing treatment. --- -// Cross-owner like /admin/cases, but gated by the medewerker capability (`CanBeoordelen`, -// WP-62) rather than the admin role, and pre-filtered to the two "still open" status tags — +// --- Werkvoorraad: the behandelportal's queue of aanvragen needing treatment. --- +// Cross-owner like /admin/cases, but gated by the medewerker capability (`CanBeoordelen`) +// rather than the admin role, and pre-filtered to the two "still open" status tags — // a behandelaar never needs to see a Concept (not their business yet) or a terminal case. api.MapGet("/werkvoorraad", (HttpContext ctx, IZaakSource zaken) => Beoordelen(ctx, "werkvoorraad", () => Results.Ok(zaken.ListCases(DateTimeOffset.UtcNow) @@ -534,9 +534,9 @@ api.MapGet("/werkvoorraad", (HttpContext ctx, IZaakSource zaken) => Beoordelen(c .Produces>() .ProducesProblem(StatusCodes.Status403Forbidden); -// --- Beoordeling (WP-65): one aanvraag's case-treatment detail — read side only (recording -// a decision is WP-65's second half). Reads through IZaakSource.ListCases (no new seam method: -// adding one now would force an OpenZaak get-by-id + mapper, which is WP-66's surface) — O(n) +// --- Beoordeling: one aanvraag's case-treatment detail — read side only (recording +// a decision is the second half). Reads through IZaakSource.ListCases (no new seam method: +// adding one now would force an OpenZaak get-by-id + mapper, which is a later change's surface) — O(n) // over a POC-sized table. A Concept isn't a case a behandelaar can treat yet, so it 404s here // same as an unknown id (only /aanvragen/{id}, citizen-scoped, shows a Concept). api.MapGet("/beoordeling/{id}", (string id, HttpContext ctx, IZaakSource zaken) => @@ -546,11 +546,11 @@ api.MapGet("/beoordeling/{id}", (string id, HttpContext ctx, IZaakSource zaken) if (c is null || c.Status.Tag == "Concept") return Results.NotFound(); var docs = DocumentStore.ByIds(c.DocumentIds) .Select(d => new BeoordelingDocumentDto(d.DocumentId, d.CategoryId, d.FileName)).ToList(); - // Belt and braces: ToAdminSummaryDto already masks the local source (RB-03) and + // Belt and braces: ToAdminSummaryDto already masks the local source and // MaskTail is idempotent, but IZaakSource has a second implementation whose Owner // is mapped from OpenZaak, so this stays as the guarantee for this response. var masked = c with { Owner = Pii.MaskTail(c.Owner!, 3) }; - // WP-68 (F3): non-throwing — c.Status.Tag crosses the IZaakSource wire boundary, so an + // Non-throwing — c.Status.Tag crosses the IZaakSource wire boundary, so an // unrecognised tag degrades to "cannot decide" instead of a 500. var canBesluiten = Enum.TryParse(c.Status.Tag, out var tag) && BeoordelingRules.CanDecide(tag); var decisions = new BeoordelingDecisionsDto(canBesluiten); @@ -561,13 +561,13 @@ api.MapGet("/beoordeling/{id}", (string id, HttpContext ctx, IZaakSource zaken) .ProducesProblem(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status404NotFound); -// --- Besluit (WP-65b/66): record a behandelaar's decision, advancing the WP-63 status +// --- Besluit: record a behandelaar's decision, advancing the status // lifecycle. The local write runs against ApplicationStore directly (not the IZaakSource // seam) — same reasoning as the GET above. The transition-legality check // (BeoordelingRules.CanDecide) is the SAME function the GET's canBesluiten flag uses, -// so the two can never drift — and (WP-68 F2) it now runs inside ApplicationStore.RecordBesluit's +// so the two can never drift — and it now runs inside ApplicationStore.RecordBesluit's // write lock rather than here, so two concurrent besluiten can't both pass it before either -// writes. WP-66: once the local decision has committed, IZaakSource also gets a chance to +// writes. Once the local decision has committed, IZaakSource also gets a chance to // advance the ZGW-side zaak status — LocalZaakSource no-ops, OpenZaakZaakSource POSTs a new // Statussen entry (see its RecordBesluit). api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, HttpContext ctx, IZaakSource zaken) => @@ -575,12 +575,12 @@ api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, H { if (!Enum.TryParse(req.Besluit, out var besluit)) return Results.Problem(detail: $"Onbekend besluit '{req.Besluit}'.", statusCode: StatusCodes.Status400BadRequest); - // WP-68 (F6): moved to BeoordelingRules.RequiresToelichting — same rule, now unit-testable. + // Moved to BeoordelingRules.RequiresToelichting — same rule, now unit-testable. if (BeoordelingRules.RequiresToelichting(besluit) && string.IsNullOrWhiteSpace(req.Toelichting)) return Results.Problem(detail: "Toelichting is verplicht bij dit besluit.", statusCode: StatusCodes.Status400BadRequest); var now = DateTimeOffset.UtcNow; - // Real bug fix (WP-66): `id` is the FE-facing case id from IZaakSource.ListCases — under + // Real bug fix: `id` is the FE-facing case id from IZaakSource.ListCases — under // OpenZaakZaakSource that's the ZGW zaak's own uuid, not this store's primary key (a // ListCases lookup, not ApplicationStore.GetAny(id), same seam the GET sibling above // uses), so resolve the case first and go to the local Aanvraag via its Referentie @@ -597,12 +597,12 @@ api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, H statusCode: StatusCodes.Status409Conflict); app.Logger.LogInformation("aanvraag besluit id={Id} besluit={Besluit}", a.Id, besluit); - // RB-07/BIO-007: the gate above records that a behandelaar was allowed to act; this + // BIO-007: the gate above records that a behandelaar was allowed to act; this // records what they decided. Without it /beheer/audit cannot answer "who rejected this // aanvraag", which is the question the trail exists for. AuditAuthz(ctx, "aanvraag:besluit", $"aanvraag/{a.Id}/{besluit}", true, Authz.ResolvePrincipal(ctx)); - // WP-60: the local decision above already committed — a ZGW failure here is caught and + // The local decision above already committed — a ZGW failure here is caught and // flagged rather than allowed to diverge silently, same handling as submit's create-zaak // and document-link writes. try @@ -611,7 +611,7 @@ api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, H } catch (Exception ex) { - // WP-73: Aanvraag.Decided's Referentie is required/non-null — no `?? a.Id` fallback needed. + // Aanvraag.Decided's Referentie is required/non-null — no `?? a.Id` fallback needed. RecordZgwDivergence(ctx, a.Id, updated!.Referentie, ex); } @@ -625,7 +625,7 @@ api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, H .Produces(StatusCodes.Status404NotFound); // OpenZaak's Notificaties API (NRC) calls this on every zaak event once an `abonnement` is -// provisioned (WP-52, out-of-band — see openzaak-integration.md, no app code subscribes it). +// provisioned (out-of-band — see openzaak-integration.md, no app code subscribes it). // The caller is NRC, not a user: no Principal, so this audits via AuthzAuditStore directly // rather than the Principal-shaped AuditAuthz helper below. A plain shared secret (not a // JWT — that's only for this BFF's OUTBOUND ZGW calls) compared in fixed time; an unconfigured @@ -656,7 +656,7 @@ api.MapPost("/zgw/notificaties", (HttpContext ctx, NotificatieDto body) => // PRD-0002 §6: coarse, role-derived capabilities for nav/menu-level checks (NOT // tied to a specific brief's live status — see BriefDecisionsDto for that). -// WP-64: `aanvraag:beoordelen` is caller-kind-derived (CanBeoordelen), not role-derived like +// `aanvraag:beoordelen` is caller-kind-derived (CanBeoordelen), not role-derived like // the rest of RoleCapabilities — appended here rather than folded into that switch, since it // depends on CallerIdentity (medewerker rollen), not the dev X-Role stand-in. api.MapGet("/me", (HttpContext ctx) => @@ -667,7 +667,7 @@ api.MapGet("/me", (HttpContext ctx) => }) .Produces(); -// Feature flags (WP-47). GET is readable by any principal (it drives FE gating); the toggle is +// Feature flags. GET is readable by any principal (it drives FE gating); the toggle is // admin-only. Catalog is code; state is the runtime override in SQLite. api.MapGet("/flags", () => Results.Ok(FeatureFlagStore.All().Select(f => new FeatureFlagDto(f.Key, f.Description, f.Enabled)).ToList())) @@ -690,7 +690,7 @@ api.MapPut("/admin/flags/{key}", (string key, SetFeatureFlagRequest req, HttpCon api.MapGet("/brief", (HttpContext ctx) => { - // RB-23/CQ-007: a read that used to allocate a row on first call. The owner's first + // CQ-007: a read that used to allocate a row on first call. The owner's first // draft now comes only from the explicit POST /brief/reset (BriefStore.ResetAndCreate) // — this GET is a pure query and 404s when there is nothing to read yet. var e = BriefStore.Get(ctx.Zorgverlener().Bsn); @@ -700,13 +700,13 @@ api.MapGet("/brief", (HttpContext ctx) => .Produces() .Produces(StatusCodes.Status404NotFound); -// Server-rendered HTML preview (WP-25): "what you compose is what is sent" — the +// Server-rendered HTML preview: "what you compose is what is sent" — the // same LetterHtml.Render a sent brief archived. Hand-written on the FE (fetch → // blob → new tab), so excluded from the OpenAPI doc, same seam as uploads. Sent // letters serve their frozen archive; anything else renders live with a watermark. api.MapGet("/brief/preview", (HttpContext ctx) => { - // RB-23: BriefStore.GetOrCreate is gone (split into Get + ResetAndCreate). This GET + // BriefStore.GetOrCreate is gone (split into Get + ResetAndCreate). This GET // must not create a brief as a side effect either, so it 404s under the same // precondition as GET /brief — in the running app the FE only reaches this endpoint // from the brief page, which has already loaded (and, if needed, reset) a brief. @@ -785,7 +785,7 @@ api.MapPost("/brief/reveal-bignummer", (HttpContext ctx) => var canReveal = Authz.CanRevealBigNummer(principal); var steppedUp = ctx.Request.Headers["X-Step-Up"] == "true"; var allowed = canReveal && steppedUp; - // RB-02/BIO-008: the resource ref is the brief, not the subject — a BSN concatenated + // BIO-008: the resource ref is the brief, not the subject — a BSN concatenated // here lands in a persisted, admin-visible column the "no PII" guarantee covers. One // brief exists per owner, so the id added nothing the acting principal did not imply. AuditAuthz(ctx, "brief:reveal-bignummer", "brief", allowed, principal); @@ -810,7 +810,7 @@ api.MapPost("/brief/reset", (HttpContext ctx) => .WithName("briefReset") .Produces(); -// --- Organization templates (WP-23): the second template axis — appearance and +// --- Organization templates: the second template axis — appearance and // identity per sub-organization. Admin-only (X-Role: admin, the same dev-stub seam // as drafter/approver); the same Authz check gates every endpoint and feeds the // `orgtemplate:edit` capability on /me, so emit and enforce cannot drift. --- @@ -886,7 +886,7 @@ app.Run(); // One gate for every org-template endpoint — the enforce twin of the // `orgtemplate:edit` capability RoleCapabilities emits (single Authz source). // -// RB-07/BIO-007: every gate below audits the real decision, allow *and* deny. Auditing +// BIO-007: every gate below audits the real decision, allow *and* deny. Auditing // only denials left /beheer/audit able to answer "who was turned away" but not "who // changed this", which for a register whose integrity is the product is the wrong half // (PRD-0002 §8 lists approvals alongside denials). The allow row is written by the gate, @@ -914,7 +914,7 @@ IResult StamdataAdmin(HttpContext ctx, Func action) } // One gate for every admin-cases endpoint — the enforce twin of the `cases:manage` -// capability RoleCapabilities emits (single Authz source, WP-36). A denial is audited. +// capability RoleCapabilities emits (single Authz source). A denial is audited. IResult CasesAdmin(HttpContext ctx, Func action) { var principal = Authz.ResolvePrincipal(ctx); @@ -925,8 +925,8 @@ IResult CasesAdmin(HttpContext ctx, Func action) statusCode: StatusCodes.Status403Forbidden); } -// One gate for every behandelaar endpoint (werkvoorraad, WP-64; beoordeling detail, WP-65) — -// the enforce twin of `CanBeoordelen` (WP-62). Unlike the other *Admin gates above, this +// One gate for every behandelaar endpoint (werkvoorraad; beoordeling detail) — +// the enforce twin of `CanBeoordelen`. Unlike the other *Admin gates above, this // checks the CallerIdentity directly (medewerker rollen), not a role-only Principal — a // zorgverlener with X-Role=admin still gets denied. `resource` feeds the denial's audit row. IResult Beoordelen(HttpContext ctx, string resource, Func action) @@ -938,7 +938,7 @@ IResult Beoordelen(HttpContext ctx, string resource, Func action) statusCode: StatusCodes.Status403Forbidden); } -// One gate for the feature-flag toggle — the enforce twin of `flags:manage` (WP-47). Takes a +// One gate for the feature-flag toggle — the enforce twin of `flags:manage`. Takes a // per-call `resource` like Beoordelen does, because the toggle endpoint writes no log line of // its own (BIO-007): a bare "feature-flags" row would say a flag changed without saying which, // and this is the surface CQ-004/ADR-C-009 hinge on. @@ -963,11 +963,11 @@ void AuditAuthz(HttpContext ctx, string action, string resource, bool allowed, P app.Logger.LogInformation( "authz action={Action} resource={Resource} decision={Decision} role={Role} correlationId={Cid}", action, resource, allowed ? "allow" : "deny", principal.Role, cid); - // Persist the queryable, data-minimised trail (WP-41) alongside the log line. + // Persist the queryable, data-minimised trail alongside the log line. AuthzAuditStore.Record(action, resource, allowed, principal.Role.ToString(), cid); } -// WP-60: the local write already committed — this records that its ZGW counterpart didn't, +// The local write already committed — this records that its ZGW counterpart didn't, // rather than letting the two sides diverge silently (openzaak-integration.md's "Write // resilience" section). Same audit trail AuditAuthz writes to (/beheer/audit), so a // divergence is visible next to every other decision, not a separate mechanism. @@ -986,7 +986,7 @@ BriefViewDto ToView(HttpContext ctx, BriefEntity e) => new( BriefSeed.PassagesFor(e.Beroep), Authz.Decisions(Authz.ResolvePrincipal(ctx), e.Status.Tag, e.DrafterId), // Sent letters render with the version pinned at send; everything else follows - // the sub-org's current published template (WP-23 immutability invariant). + // the sub-org's current published template (immutability invariant). OrgTemplateStore.TemplateForBrief(e.SubOrgId, e.Status.Tag == "sent" ? e.SentOrgTemplateVersion : null), // The case this letter is about — joined from the seeded zorgverlener so the // behandel scherm can show whom/what it concerns without brief/ importing registratie. @@ -1003,9 +1003,9 @@ IResult BriefResult(HttpContext ctx, (BriefStore.Outcome outcome, BriefEntity? e _ => Results.Problem(detail: "Ongeldige overgang voor de huidige status van de brief.", statusCode: StatusCodes.Status409Conflict), }; -// RB-07/BIO-007: every brief transition already funnelled through here for its log line, +// BIO-007: every brief transition already funnelled through here for its log line, // so the audit row goes here too — a fifth transition cannot be added that logs but leaves -// no trail. Resource is the bare "brief" (RB-02: never the owner's BSN); the decision is +// no trail. Resource is the bare "brief" (never the owner's BSN); the decision is // the transition's own outcome, so a 403 or a 409 is as visible as a success. void LogBrief(HttpContext ctx, string action, (BriefStore.Outcome outcome, BriefEntity? entity) r) { @@ -1019,7 +1019,7 @@ void LogBrief(HttpContext ctx, string action, (BriefStore.Outcome outcome, Brief // real system ships this to structured logging / an audit store). A repeated // Idempotency-Key short-circuits to the first call's result — see IdempotencyStore // — so a retried submit dedupes instead of minting a second reference. The key is -// scoped to the caller (RB-18/BIO-018): two callers who happen to send the same +// scoped to the caller (BIO-018): two callers who happen to send the same // client-chosen header value do not share a cached result. IResult Submit(HttpContext ctx, string kind, string? reject, IReadOnlyList? documents = null) { @@ -1062,7 +1062,7 @@ IResult Submit(HttpContext ctx, string kind, string? reject, IReadOnlyList -/// The Notificaties API (NRC) webhook body (WP-52) — the standard ZGW notification shape POSTed +/// The Notificaties API (NRC) webhook body — the standard ZGW notification shape POSTed /// to a subscribed abonnement's callbackUrl on every zaak event. Only /// (the zaak's URL — not PII) is read today, for the audit trail; the /// rest is parsed because it's the real payload shape a live OpenZaak actually sends, not diff --git a/backend/src/BigRegister.Api/Zgw/OpenZaakDocumentSource.cs b/backend/src/BigRegister.Api/Zgw/OpenZaakDocumentSource.cs index 65bb715..855ea55 100644 --- a/backend/src/BigRegister.Api/Zgw/OpenZaakDocumentSource.cs +++ b/backend/src/BigRegister.Api/Zgw/OpenZaakDocumentSource.cs @@ -9,10 +9,10 @@ using Microsoft.Extensions.Logging; namespace BigRegister.Api.Zgw; /// -/// The backed by a real OpenZaak / ZGW Documenten API (DRC, -/// WP-51). An upload always lands locally first ( stays the record +/// The backed by a real OpenZaak / ZGW Documenten API (DRC). +/// An upload always lands locally first ( stays the record /// of truth for preview/download/audit, same reasoning as 's -/// dual-write for aanvragen, WP-50) and is then ALSO registered as a DRC +/// dual-write for aanvragen) and is then ALSO registered as a DRC /// enkelvoudiginformatieobject, whose url is persisted () /// so can find it later without a re-upload. Selected only when /// Zgw:Enabled=true; the default stays . @@ -27,7 +27,7 @@ public sealed class OpenZaakDocumentSource( { private readonly ZgwHttpClient zgw = new(http, tokens); - // WP-59: per-document-type confidentiality (stamdata, ADR-0004) — "openbaar" if the + // Per-document-type confidentiality (stamdata, ADR-0004) — "openbaar" if the // category isn't in the table, so an unconfigured category never fails the upload. private static readonly IReadOnlyDictionary ConfidentialiteitByCategory = StamdataFile.Load("documentconfidentialiteit") @@ -44,7 +44,7 @@ public sealed class OpenZaakDocumentSource( UploadAsync(localId, categoryId, wizardId, fileName, contentType, content, caller) .GetAwaiter().GetResult(); - // WP-60: once DocumentStore.Add (below) has committed, the local document is the record of + // Once DocumentStore.Add (below) has committed, the local document is the record of // truth (per the class doc above) — a ZGW failure past that point is caught, logged, and // leaves DrcUrl null rather than throwing. DrcUrl == null is already the meaningful "not // registered in ZGW yet" detector LinkToZaak skips on, so no separate flag column is needed @@ -89,7 +89,7 @@ public sealed class OpenZaakDocumentSource( /// once a zaak exists, POST a zaakinformatieobject for every document that has a DRC url — /// documents uploaded before Zgw:Enabled was ever true (or under a config gap) simply have /// no DrcUrl yet and are skipped, matching "nothing extra to link" for the local case. - /// WP-60: unlike Upload, a ZGW failure here still throws — DocumentStore.Link (the local + /// Unlike Upload, a ZGW failure here still throws — DocumentStore.Link (the local /// half) already ran above, so the caller (Program.cs's submit endpoint) catching this and /// recording it as a flagged divergence is what closes the gap, not a try/catch in here. public void LinkToZaak(IReadOnlyList documentIds, string? zaakUrl, CallerIdentity caller) diff --git a/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs b/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs index 2e9b260..bffd663 100644 --- a/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs +++ b/backend/src/BigRegister.Api/Zgw/OpenZaakZaakSource.cs @@ -15,7 +15,7 @@ public sealed record ZgwPage( [property: JsonPropertyName("results")] IReadOnlyList Results); /// -/// The backed by a real OpenZaak / ZGW Zaken API (WP-49 read, WP-50 +/// The backed by a real OpenZaak / ZGW Zaken API (read and /// write). Reads zaken (following pagination), maps each zaak's zaaktype URL back to the /// internal aanvraag-type key via Zgw:ZaaktypeUrls (a local lookup — NOT OpenZaak's /// human zaaktype label, which isn't a value 's @@ -39,7 +39,7 @@ public sealed class OpenZaakZaakSource(HttpClient http, ZgwTokenProvider tokens, public IReadOnlyList ListCases(DateTimeOffset now) => ListCasesAsync(bsn: null, caller: null).GetAwaiter().GetResult(); - /// WP-53: same read, filtered to one citizen's own zaken via ZGW's rol filter param + /// Same read, filtered to one citizen's own zaken via ZGW's rol filter param /// (see ) — and minted with that citizen's identity, not the /// system-level one uses. public IReadOnlyList ListMyCases(ZorgverlenerCaller caller, DateTimeOffset now) => @@ -81,14 +81,14 @@ public sealed class OpenZaakZaakSource(HttpClient http, ZgwTokenProvider tokens, return all; } - // --- Write path (WP-50): create a Zaak, then a Status, then a Rol ------------------------ + // --- Write path: create a Zaak, then a Status, then a Rol ------------------------ /// Create a zaak for a just-submitted aanvraag: POST zaak → resolve + POST the /// initial status → resolve + POST the initiator rol (BSN). Sync-over-async for the same /// reason as (see the ponytail note there) — a submit is already a /// single request/response round trip, so no extra concurrency concern. /// - /// WP-60: still no compensating transaction — if any call here throws (after + /// Still no compensating transaction — if any call here throws (after /// 's retry gives up), the aanvraag stays Submitted locally with /// no zaak; rolling it back risks an orphan zaak if the failure landed after the zaak POST /// succeeded. The caller (Program.cs's submit endpoint) catches this and records it as a @@ -108,7 +108,7 @@ public sealed class OpenZaakZaakSource(HttpClient http, ZgwTokenProvider tokens, Bronorganisatie: options.Bronorganisatie, VerantwoordelijkeOrganisatie: options.VerantwoordelijkeOrganisatie, Startdatum: DateOnly.FromDateTime(now.UtcDateTime), - // WP-73: Aanvraag.Submitted's Referentie is a required, non-nullable member — a + // Aanvraag.Submitted's Referentie is a required, non-nullable member — a // just-submitted aanvraag always has one, so there is nothing left to null-check here. Identificatie: aanvraag.Referentie), caller); @@ -140,19 +140,19 @@ public sealed class OpenZaakZaakSource(HttpClient http, ZgwTokenProvider tokens, return first.Url; } - // --- Write path (WP-66): record a behandelaar's decision as a new zaak status ----------- + // --- Write path: record a behandelaar's decision as a new zaak status ----------- /// POST a new Statussen entry to 's zaak, carrying the - /// besluit (+ toelichting) in statustoelichting — the harness's catalogus (WP-56) + /// besluit (+ toelichting) in statustoelichting — the harness's catalogus /// provisions only a begin/eind statustype pair per zaaktype, not one per decision outcome /// (a real deployment's Besluiten API is future work, see openzaak-integration.md), so this - /// reuses the SAME "last statustype" resolution WP-50's create uses for "first", rather than + /// reuses the SAME "last statustype" resolution the create path above uses for "first", rather than /// adding a besluittype abstraction this catalogus doesn't have. No-op if this aanvraag never /// got a zaak (Zgw was off at submit time, or the create diverged) — same "nothing to do" /// skip uses for a null zaakUrl. Sync-over-async /// for the same reason as . /// - /// WP-60: no compensating transaction here either — the local decision already committed + /// No compensating transaction here either — the local decision already committed /// (ApplicationStore.RecordBesluit, called by the endpoint before this). A failure here /// is caught by the endpoint and recorded as a flagged divergence (Aanvraag.ZgwError), /// the same way the submit endpoint's create-zaak/document writes are. diff --git a/backend/src/BigRegister.Api/Zgw/ZgwHttpClient.cs b/backend/src/BigRegister.Api/Zgw/ZgwHttpClient.cs index 6882c5d..87664e4 100644 --- a/backend/src/BigRegister.Api/Zgw/ZgwHttpClient.cs +++ b/backend/src/BigRegister.Api/Zgw/ZgwHttpClient.cs @@ -7,15 +7,15 @@ namespace BigRegister.Api.Zgw; /// /// Shared GET/POST-with-Bearer-JWT plumbing for the ZGW source classes. Factored out of -/// once OpenZaakDocumentSource (WP-51) needed the +/// once OpenZaakDocumentSource needed the /// identical auth + JSON + error-handling boilerplate — every ZGW call mints a fresh token /// () and expects/returns JSON. is -/// optional (WP-53): omitted for calls not tied to one citizen (metadata lookups, the admin +/// optional: omitted for calls not tied to one citizen (metadata lookups, the admin /// cross-owner list), which mint with the BFF's own system identity instead. /// internal sealed class ZgwHttpClient(HttpClient http, ZgwTokenProvider tokens) { - // WP-60: bounded retry for transport-shaped failures only (gateway restarts, timeouts) — + // Bounded retry for transport-shaped failures only (gateway restarts, timeouts) — // never a substitute for reconciliation. 3 attempts, doubling from 200ms. private const int MaxAttempts = 3; private static readonly TimeSpan BaseDelay = TimeSpan.FromMilliseconds(200); @@ -73,7 +73,7 @@ internal sealed class ZgwHttpClient(HttpClient http, ZgwTokenProvider tokens) continue; } - // RB-05/BIO-009: path only — no query string, no response-body snippet. The + // BIO-009: path only — no query string, no response-body snippet. The // BSN-filtered zaken list puts a BSN in the query, and OpenZaak echoes the request in // its error bodies, so both used to reach a message Program.cs persists as a flagged // divergence and writes to the application log. Status + path routes the failure; @@ -87,7 +87,7 @@ internal sealed class ZgwHttpClient(HttpClient http, ZgwTokenProvider tokens) /// The path without its query string — ZGW filters travel as query parameters and /// one of them is a BSN (rol__betrokkeneIdentificatie__natuurlijkPersoon__inpBsn), so no - /// ZGW url may be interpolated into a message that is logged or persisted (RB-05). + /// ZGW url may be interpolated into a message that is logged or persisted. private static string Redact(string url) => Uri.TryCreate(url, UriKind.Absolute, out var u) ? u.GetLeftPart(UriPartial.Path) : url.Split('?')[0]; @@ -103,8 +103,8 @@ internal sealed class ZgwHttpClient(HttpClient http, ZgwTokenProvider tokens) req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // Every ZGW request must declare a coordinate reference system, even when no geometry is // involved (Zaak has an optional zaakgeometrie) — a real OpenZaak 412s ("Content-Crs - // header ontbreekt") without it. Only surfaced by WP-54's live harness: the fixture/stub - // tests never modelled this header, so this bug shipped unnoticed since WP-49/50. + // header ontbreekt") without it. Only surfaced by a live harness: the fixture/stub + // tests never modelled this header, so this bug shipped unnoticed for a long time. req.Headers.Add("Accept-Crs", "EPSG:4326"); if (req.Content is not null) req.Content.Headers.Add("Content-Crs", "EPSG:4326"); } diff --git a/backend/src/BigRegister.Api/Zgw/ZgwOptions.cs b/backend/src/BigRegister.Api/Zgw/ZgwOptions.cs index b525a87..c83e36e 100644 --- a/backend/src/BigRegister.Api/Zgw/ZgwOptions.cs +++ b/backend/src/BigRegister.Api/Zgw/ZgwOptions.cs @@ -1,14 +1,14 @@ namespace BigRegister.Api.Zgw; /// -/// Config for connecting to OpenZaak / the ZGW APIs (WP-49), bound from the Zgw +/// Config for connecting to OpenZaak / the ZGW APIs, bound from the Zgw /// section of appsettings. Disabled by default so the POC runs fully offline on the local /// SQLite store; set Zgw:Enabled=true (plus the URLs + credentials) to source cases /// from a real OpenZaak. /// -/// The ZGW standard is FIVE separate services, each its own base URL — slice 1 (WP-49) only +/// The ZGW standard is FIVE separate services, each its own base URL — the first slice only /// needed the Zaken API (ZRC) and, to resolve human labels for a zaaktype, the Catalogi API -/// (ZTC). WP-50 (create-zaak) stayed on those two; WP-51 adds the Documenten API (DRC); WP-52 +/// (ZTC). Create-zaak stayed on those two; a later slice adds the Documenten API (DRC); another /// adds the Notificaties API (NRC) — inbound only, see . /// BRC arrives with a later slice, if ever. /// @@ -35,32 +35,32 @@ public sealed class ZgwOptions public string UserRepresentation { get; init; } = "BIG-register BFF"; /// Aanvraag Type (registratie/herregistratie/intake) → zaaktype URL (Catalogi), - /// so create-zaak (WP-50) knows which zaaktype to open per wizard. OpenZaak validates the URL + /// so create-zaak knows which zaaktype to open per wizard. OpenZaak validates the URL /// by fetching it, so an unconfigured or wrong entry fails loudly at create time. public Dictionary ZaaktypeUrls { get; init; } = new(); - /// RSIN of the organisation registering the zaak (bronorganisatie, WP-50). + /// RSIN of the organisation registering the zaak (bronorganisatie). public string Bronorganisatie { get; init; } = ""; - /// RSIN of the organisation responsible for the zaak (verantwoordelijkeOrganisatie, - /// WP-50) — usually the same RSIN as . + /// RSIN of the organisation responsible for the zaak (verantwoordelijkeOrganisatie) + /// — usually the same RSIN as . public string VerantwoordelijkeOrganisatie { get; init; } = ""; - /// Documenten API (DRC) base URL, e.g. https://open-zaak.example/documenten/api/v1 (WP-51). + /// Documenten API (DRC) base URL, e.g. https://open-zaak.example/documenten/api/v1. public string DrcBaseUrl { get; init; } = ""; /// Upload CategoryId (diploma/identiteit/taalvaardigheid/...) → informatieobjecttype - /// URL (Catalogi), so create-document (WP-51) knows which type to register per category — + /// URL (Catalogi), so create-document knows which type to register per category — /// the document analogue of . public Dictionary InformatieobjecttypeUrls { get; init; } = new(); - /// Notificaties API (NRC) base URL (WP-52) — documentation/provisioning only, no + /// Notificaties API (NRC) base URL — documentation/provisioning only, no /// code in this app calls it: subscribing an abonnement is a one-time out-of-band /// step (see openzaak-integration.md), not something the BFF does at runtime. public string NrcBaseUrl { get; init; } = ""; /// The exact Authorization header value NRC must send on every - /// POST /zgw/notificaties callback (WP-52) — a plain shared secret set into the + /// POST /zgw/notificaties callback — a plain shared secret set into the /// abonnement's auth field when provisioning, not a JWT. Empty (the default) /// means every notification is rejected — an unconfigured secret must never mean "accept /// anything". diff --git a/backend/src/BigRegister.Api/Zgw/ZgwTokenProvider.cs b/backend/src/BigRegister.Api/Zgw/ZgwTokenProvider.cs index ddaf95c..3c15153 100644 --- a/backend/src/BigRegister.Api/Zgw/ZgwTokenProvider.cs +++ b/backend/src/BigRegister.Api/Zgw/ZgwTokenProvider.cs @@ -6,7 +6,7 @@ using BigRegister.Domain.Authorization; namespace BigRegister.Api.Zgw; /// -/// Mints the JWT that authenticates the BFF to the ZGW APIs (WP-49). OpenZaak expects a +/// Mints the JWT that authenticates the BFF to the ZGW APIs. OpenZaak expects a /// short-lived HS256 assertion signed with the client secret, carrying iss/ /// client_id (identity), iat (issued-at), and user_id/ /// user_representation (for the ZGW audit trail). There is no OAuth refresh dance — @@ -23,12 +23,12 @@ public sealed class ZgwTokenProvider(ZgwOptions options) /// specific citizen (e.g. the admin cross-owner ListCases). public string Mint() => MintCore(options.UserId, options.UserRepresentation); - /// Per-request variant (WP-53, extended WP-62): the ZGW audit trail (user_id/ + /// Per-request variant: the ZGW audit trail (user_id/ /// user_representation) reflects the acting caller instead of this BFF's static /// config identity, for any call made on a specific caller's behalf (create zaak, upload, /// link, citizen-scoped list). is the BSN for a - /// zorgverlener or the medewerkerId for a medewerker (WP-66 mints this for a besluit write - /// the same way, with no further change needed here). + /// zorgverlener or the medewerkerId for a medewerker (a besluit write mints this the + /// same way, with no further change needed here). public string Mint(CallerIdentity caller) => MintCore(caller.SubjectId, caller.DisplayName); private string MintCore(string userId, string userRepresentation) diff --git a/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs b/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs index 430ebb5..100f7f7 100644 --- a/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs +++ b/backend/src/BigRegister.Api/Zgw/ZgwZaakMapper.cs @@ -21,7 +21,7 @@ public sealed record ZgwZaak( /// /// Anti-corruption map: ZGW Zaak → the existing the FE -/// already renders (WP-49). This is where "URL as identity" and the cross-service zaaktype +/// already renders. This is where "URL as identity" and the cross-service zaaktype /// join get flattened away, so nothing downstream (the FE) sees ZGW shapes. /// public static class ZgwZaakMapper @@ -45,7 +45,7 @@ public static class ZgwZaakMapper Id: Uuid(z.Url), Type: zaaktypeLabel, Status: status, - DocumentIds: Array.Empty(), // zaak↔document links arrive with WP-51 (DRC) + DocumentIds: Array.Empty(), // zaak↔document links arrive with a later slice (DRC) CreatedAt: created, UpdatedAt: updated, SubmittedAt: created, @@ -57,7 +57,7 @@ public static class ZgwZaakMapper private static string Iso(DateOnly d) => d.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc).ToString("o"); - /// Status for a zaak that was JUST created (WP-50) — always the open/InBehandeling + /// Status for a zaak that was JUST created — always the open/InBehandeling /// coarse status (no einddatum yet), same convention as . public static AanvraagStatusDto ToCreatedStatusDto(string identificatie) => AanvraagStatus.InBehandeling(identificatie, manual: true).ToDto(); diff --git a/backend/src/BigRegister.Api/appsettings.json b/backend/src/BigRegister.Api/appsettings.json index 49c9307..4ac3ff6 100644 --- a/backend/src/BigRegister.Api/appsettings.json +++ b/backend/src/BigRegister.Api/appsettings.json @@ -6,7 +6,7 @@ } }, "AllowedHosts": "*", - "_Zgw": "WP-49..52: set Enabled=true + the URLs/credentials/RSINs/type maps to source, create and document cases against a real OpenZaak; NrcBaseUrl/NotificatieAuthorization configure the inbound notificaties webhook. Off = local SQLite store (offline POC default).", + "_Zgw": "Set Enabled=true + the URLs/credentials/RSINs/type maps to source, create and document cases against a real OpenZaak; NrcBaseUrl/NotificatieAuthorization configure the inbound notificaties webhook. Off = local SQLite store (offline POC default).", "Zgw": { "Enabled": false, "ZrcBaseUrl": "", diff --git a/backend/tests/BigRegister.Tests/AanvraagTests.cs b/backend/tests/BigRegister.Tests/AanvraagTests.cs index 551abdc..f93ebaa 100644 --- a/backend/tests/BigRegister.Tests/AanvraagTests.cs +++ b/backend/tests/BigRegister.Tests/AanvraagTests.cs @@ -14,7 +14,7 @@ public class AanvraagTests(TestWebApplicationFactory factory) : IClassFixture Create(string type = "registratie") { - // WP-35: one Concept per type is now server-enforced, and these tests share one DB + // One Concept per type is now server-enforced, and these tests share one DB // (IClassFixture). Clear any leftover Concept so each test starts from a clean slate. var existing = await List(); Assert.NotNull(existing); @@ -103,7 +103,7 @@ public class AanvraagTests(TestWebApplicationFactory factory) : IClassFixture UploadAs(HttpClient client, string owner, string localId) diff --git a/backend/tests/BigRegister.Tests/Acceptance/BesluitLifecycleTests.cs b/backend/tests/BigRegister.Tests/Acceptance/BesluitLifecycleTests.cs index 807ce5c..30688d2 100644 --- a/backend/tests/BigRegister.Tests/Acceptance/BesluitLifecycleTests.cs +++ b/backend/tests/BigRegister.Tests/Acceptance/BesluitLifecycleTests.cs @@ -9,8 +9,8 @@ using BigRegister.Tests.Builders; namespace BigRegister.Tests.Acceptance; /// -/// Behaviour-level tests for the besluit lifecycle (WP-65b/66/68), built through the -/// type-state builder (WP-70) rather than the full wizard/upload dance +/// Behaviour-level tests for the besluit lifecycle, built through the +/// type-state builder rather than the full wizard/upload dance /// uses — a fixture that's already Submitted (or already /// Decided) is a two-line Given, not fifteen. Each test persists its own Given-built /// straight into the isolated per-class SQLite file (no HTTP round trip diff --git a/backend/tests/BigRegister.Tests/Acceptance/IntakeSubmissionTests.cs b/backend/tests/BigRegister.Tests/Acceptance/IntakeSubmissionTests.cs index bdb0bc7..51ea0ee 100644 --- a/backend/tests/BigRegister.Tests/Acceptance/IntakeSubmissionTests.cs +++ b/backend/tests/BigRegister.Tests/Acceptance/IntakeSubmissionTests.cs @@ -8,8 +8,8 @@ using BigRegister.Tests.Builders; namespace BigRegister.Tests.Acceptance; /// -/// Behaviour-level tests for the scholing-threshold enforcement (WP-69) over -/// POST /aanvragen/{id}/submit (the wizard's real path — WP-72 deleted the legacy +/// Behaviour-level tests for the scholing-threshold enforcement over +/// POST /aanvragen/{id}/submit (the wizard's real path — a later change deleted the legacy /// POST /intakes endpoint this once also covered). Built through the type-state builder, mirroring rather /// than the full wizard/upload dance — the builder's default owner IS -/// Contract test for the FE/BE seam on phone-number stripping (WP-75). Both sides share +/// Contract test for the FE/BE seam on phone-number stripping. Both sides share /// the same format regex (^0\d{9}$) but, until this test, diverged on what they /// strip before checking it: the FE's parseTelefoonnummer /// (registratie/domain/value-objects/telefoonnummer.ts) also drops parentheses and maps a diff --git a/backend/tests/BigRegister.Tests/AdminCasesTests.cs b/backend/tests/BigRegister.Tests/AdminCasesTests.cs index afd1816..3856f7e 100644 --- a/backend/tests/BigRegister.Tests/AdminCasesTests.cs +++ b/backend/tests/BigRegister.Tests/AdminCasesTests.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Testing; namespace BigRegister.Tests; -/// WP-36: admin cross-owner case list + admin delete, gated by `cases:manage`. +/// Admin cross-owner case list + admin delete, gated by `cases:manage`. public class AdminCasesTests(TestWebApplicationFactory factory) : IClassFixture { private readonly HttpClient _client = factory.CreateClient(); @@ -35,7 +35,7 @@ public class AdminCasesTests(TestWebApplicationFactory factory) : IClassFixture< list.EnsureSuccessStatusCode(); var cases = (await list.Content.ReadFromJsonAsync>())!; var mine = cases.Single(x => x.Id == a.Id); - // RB-03/BIO-003: the owner is carried, but masked — it is a BSN, and this list is + // BIO-003: the owner is carried, but masked — it is a BSN, and this list is // read by someone who is not the subject. Assert.Equal("******782", mine.Owner); Assert.DoesNotContain(DocumentStore.DemoOwner, mine.Owner); diff --git a/backend/tests/BigRegister.Tests/AuthzAuditTests.cs b/backend/tests/BigRegister.Tests/AuthzAuditTests.cs index 1252557..1934843 100644 --- a/backend/tests/BigRegister.Tests/AuthzAuditTests.cs +++ b/backend/tests/BigRegister.Tests/AuthzAuditTests.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc.Testing; namespace BigRegister.Tests; -/// WP-41: the persisted authz/PII-reveal audit trail is queryable, data-minimised (no PII). +/// The persisted authz/PII-reveal audit trail is queryable, data-minimised (no PII). public class AuthzAuditTests(TestWebApplicationFactory factory) : IClassFixture { private readonly HttpClient _client = factory.CreateClient(); @@ -59,7 +59,7 @@ public class AuthzAuditTests(TestWebApplicationFactory factory) : IClassFixture< Assert.Contains(await AuditLog(), e => e.Action == "brief:reveal-bignummer"); } - /// RB-07/BIO-007: the trail used to record only denials, so `/beheer/audit` could answer + /// BIO-007: the trail used to record only denials, so `/beheer/audit` could answer /// "who was turned away" but not "who changed this" — for a register whose integrity is the /// product, the wrong half. Every gate now audits the real decision. [Fact] @@ -69,10 +69,10 @@ public class AuthzAuditTests(TestWebApplicationFactory factory) : IClassFixture< Assert.Contains(await AuditLog(), e => e.Action == "cases:manage" && e.Decision == "allow" && e.Role == "Admin"); } - /// RB-08/BIO-003: the admin upload delete used to be gated by a standalone X-Admin + /// BIO-003: the admin upload delete used to be gated by a standalone X-Admin /// header, outside Authz and writing no AuthzAuditStore row at all. Routing it through /// CasesAdmin (cases:manage) gives it the same allow-path row every other admin-cases - /// endpoint gets, for free, per RB-07. `CasesAdmin` audits under a fixed "cases" + /// endpoint gets, for free. `CasesAdmin` audits under a fixed "cases" /// resource shared with the other admin-cases endpoints, so this asserts a **count** /// increase — reading the store directly (not via `GET /admin/audit`, itself a /// `CasesAdmin` endpoint that would write its own row and confound the count) — @@ -120,7 +120,7 @@ public class AuthzAuditTests(TestWebApplicationFactory factory) : IClassFixture< Assert.Contains(await AuditLog(), e => e.Action == "brief:submit" && e.Decision == "deny"); } - /// RB-02/BIO-008: the schema test below asserts on **column names**, so a BSN inside a + /// BIO-008: the schema test below asserts on **column names**, so a BSN inside a /// column called `Resource` was invisible to it — and one was there, concatenated as /// `"brief/" + Bsn`. This asserts on the stored **values** instead. Four documents /// promise this trail holds no PII; this is the test that makes the promise checkable. diff --git a/backend/tests/BigRegister.Tests/AuthzTests.cs b/backend/tests/BigRegister.Tests/AuthzTests.cs index 2b2241c..9d89537 100644 --- a/backend/tests/BigRegister.Tests/AuthzTests.cs +++ b/backend/tests/BigRegister.Tests/AuthzTests.cs @@ -82,7 +82,7 @@ public class AuthzTests Assert.False(Authz.Decisions(Approver, "draft", DrafterId).CanRevealBigNummer); } - // --- CanBeoordelen (WP-62) -------------------------------------------------------------- + // --- CanBeoordelen -------------------------------------------------------------- [Fact] public void CanBeoordelen_true_for_a_medewerker_with_the_behandelaar_rol() diff --git a/backend/tests/BigRegister.Tests/BeoordelingTests.cs b/backend/tests/BigRegister.Tests/BeoordelingTests.cs index 23334f9..7e894ce 100644 --- a/backend/tests/BigRegister.Tests/BeoordelingTests.cs +++ b/backend/tests/BigRegister.Tests/BeoordelingTests.cs @@ -7,8 +7,8 @@ using Microsoft.AspNetCore.Mvc.Testing; namespace BigRegister.Tests; -/// WP-65 (read side): one aanvraag's case-treatment detail, gated by the same medewerker -/// capability (`CanBeoordelen`, WP-62) as the werkvoorraad list (WP-64). +/// One aanvraag's case-treatment detail (read side), gated by the same medewerker +/// capability (`CanBeoordelen`) as the werkvoorraad list. public class BeoordelingTests(TestWebApplicationFactory factory) : IClassFixture { private readonly HttpClient _client = factory.CreateClient(); @@ -152,7 +152,7 @@ public class BeoordelingTests(TestWebApplicationFactory factory) : IClassFixture Assert.Equal("Goedgekeurd", view.Aanvraag.Status.Tag); Assert.False(view.Decisions.CanBesluiten); // terminal — no further decision allowed - // RB-07/BIO-007: the gate records that a behandelaar was allowed to act; this records + // BIO-007: the gate records that a behandelaar was allowed to act; this records // what they decided, which is the question /beheer/audit exists to answer. Assert.Contains(AuthzAuditStore.List(), e => e.Action == "aanvraag:besluit" && e.Decision == "allow" && @@ -222,7 +222,7 @@ public class BeoordelingTests(TestWebApplicationFactory factory) : IClassFixture } } - // WP-68 (F2): the transition-legality check now runs inside RecordBesluit's write lock, so + // The transition-legality check now runs inside RecordBesluit's write lock, so // two besluiten racing on the same still-open aanvraag can't both pass the check before // either writes — exactly one commits, the other sees the now-terminal status. [Fact] diff --git a/backend/tests/BigRegister.Tests/BriefEndpointTests.cs b/backend/tests/BigRegister.Tests/BriefEndpointTests.cs index 78cdd05..61eb98a 100644 --- a/backend/tests/BigRegister.Tests/BriefEndpointTests.cs +++ b/backend/tests/BigRegister.Tests/BriefEndpointTests.cs @@ -28,7 +28,7 @@ public class BriefEndpointTests(TestWebApplicationFactory factory) : IClassFixtu return new SaveBriefRequest(sections); } - /// RB-23: `GET /brief` no longer seeds a brief on first call, so every test that + /// `GET /brief` no longer seeds a brief on first call, so every test that /// needs one present creates it explicitly through `POST /brief/reset` /// (`BriefStore.ResetAndCreate`) — the same command the "start over" affordance uses. private async Task SeedBrief() @@ -48,7 +48,7 @@ public class BriefEndpointTests(TestWebApplicationFactory factory) : IClassFixtu return req; } - // --- RB-23/CQ-007: GET /brief is a pure query — it must not create a row. --- + // --- CQ-007: GET /brief is a pure query — it must not create a row. --- [Fact] public async Task Get_returns_404_and_writes_no_row_when_no_brief_exists_for_the_owner() @@ -181,8 +181,8 @@ public class BriefEndpointTests(TestWebApplicationFactory factory) : IClassFixtu Assert.NotNull(submitted); Assert.Equal("submitted", submitted.Brief.Status.Tag); - // RB-07/BIO-007: the allow side of the transition leaves a row, not just a log line. - // Resource is the bare "brief" — never the owner's BSN (RB-02). + // BIO-007: the allow side of the transition leaves a row, not just a log line. + // Resource is the bare "brief" — never the owner's BSN. Assert.Contains(AuthzAuditStore.List(), e => e.Action == "brief:submit" && e.Decision == "allow" && e.Resource == "brief"); } diff --git a/backend/tests/BigRegister.Tests/Builders/AanvraagBuilder.cs b/backend/tests/BigRegister.Tests/Builders/AanvraagBuilder.cs index b486b3c..2d74f72 100644 --- a/backend/tests/BigRegister.Tests/Builders/AanvraagBuilder.cs +++ b/backend/tests/BigRegister.Tests/Builders/AanvraagBuilder.cs @@ -14,9 +14,9 @@ public static class TestIdentities } /// -/// Type-state test-data builder for (WP-70; simplified at WP-73). "Build +/// Type-state test-data builder for . "Build /// test data through the same door production code uses" — itself is now -/// the closed Concept/Submitted/Decided union WP-73 introduced, so this builder no longer needs +/// the closed Concept/Submitted/Decided union, so this builder no longer needs /// to mirror production's guards (step-index bounds, "Afwijzen needs a toelichting") by hand — /// it just calls the real nested constructors/required members, which enforce them. A call that /// would build an illegal Aanvraag (e.g. deciding a still-Concept aanvraag, or an Afwijzen with @@ -61,7 +61,7 @@ public sealed class ConceptAanvraag /// Submits the draft — always assigns a Referentie AND SubmittedAt together (mirrors /// ApplicationStore.Submit), so a fixture built this way can never hit the - /// null-forgiving derefs the pre-WP-73 flat Aanvraag needed (there's nothing to force any + /// null-forgiving derefs the earlier flat Aanvraag needed (there's nothing to force any /// more: both are required, non-null members of ). public SubmittedAanvraag Submitted(bool autoApprovable = false) => new(_type, _owner, autoApprovable); @@ -110,7 +110,7 @@ public sealed class SubmittedAanvraag return this; } - /// Records a behandelaar's decision. Unlike the pre-WP-73 builder, there is no + /// Records a behandelaar's decision. Unlike the earlier builder, there is no /// hand-written toelichting guard mirroring BeoordelingRules.RequiresToelichting any /// more — / /// simply have a `required string Toelichting` member; the null-coalescing throw below is the @@ -186,7 +186,7 @@ public sealed class SubmittedAanvraag /// (); a fixture that /// needs a SECOND besluit (the MeerInfoGevraagd "still decidable" case) builds fresh from /// again, exactly as a real second request would. Just a one-line -/// wrapper around the already-fully-built value — WP-73 moved +/// wrapper around the already-fully-built value — construction moved /// all the actual construction (and its invariant enforcement) into /// itself, so there's nothing left for this type to do /// except keep .Decided(...).Build() a valid two-call chain for the existing test diff --git a/backend/tests/BigRegister.Tests/Domain/ApplicationRuleTests.cs b/backend/tests/BigRegister.Tests/Domain/ApplicationRuleTests.cs index 29a1a55..e69bfe2 100644 --- a/backend/tests/BigRegister.Tests/Domain/ApplicationRuleTests.cs +++ b/backend/tests/BigRegister.Tests/Domain/ApplicationRuleTests.cs @@ -4,9 +4,9 @@ namespace BigRegister.Tests.Domain; public class ApplicationRuleTests { - // WP-63: the published lifecycle (ADR-0002) must name exactly these five tags, in this + // The published lifecycle (ADR-0002) must name exactly these five tags, in this // order — ToStatusDto's string literals must keep matching Enum.ToString(), and Ingediend/ - // MeerInfoGevraagd (unreachable until WP-65 adds the behandelaar transition) stay defined. + // MeerInfoGevraagd (unreachable until a later change adds the behandelaar transition) stay defined. [Fact] public void AanvraagStatusTag_covers_the_published_lifecycle() { diff --git a/backend/tests/BigRegister.Tests/Domain/BeoordelingRuleTests.cs b/backend/tests/BigRegister.Tests/Domain/BeoordelingRuleTests.cs index 8c263a6..d078686 100644 --- a/backend/tests/BigRegister.Tests/Domain/BeoordelingRuleTests.cs +++ b/backend/tests/BigRegister.Tests/Domain/BeoordelingRuleTests.cs @@ -16,7 +16,7 @@ public class BeoordelingRuleTests public void Only_open_statuses_are_decidable(AanvraagStatusTag tag, bool expected) => Assert.Equal(expected, BeoordelingRules.CanDecide(tag)); - // WP-68 (F6): the toelichting rule, moved here from an inline endpoint check. + // The toelichting rule, moved here from an inline endpoint check. [Theory] [InlineData(Besluit.Goedkeuren, false)] [InlineData(Besluit.Afwijzen, true)] @@ -24,12 +24,12 @@ public class BeoordelingRuleTests public void Only_a_non_approval_requires_a_toelichting(Besluit besluit, bool expected) => Assert.Equal(expected, BeoordelingRules.RequiresToelichting(besluit)); - // WP-68 (T3): the transition table at the AGGREGATE level, not just against a bare tag — + // The transition table at the AGGREGATE level, not just against a bare tag — // an Aanvraag whose BesluitStatus already records a terminal decision computes a terminal // StatusAt, and CanDecide refuses a further besluit regardless of which one. Pins the // domain statement "Afgewezen/Goedgekeurd → no further besluit" independent of the // endpoint's own (integration-level) Already_decided_case_rejects_a_further_besluit. - // WP-70: built via Given, not a hand-rolled Aanvraag literal — Decided(Besluit.Afwijzen) with + // Built via Given, not a hand-rolled Aanvraag literal — Decided(Besluit.Afwijzen) with // no toelichting simply couldn't compile as a fixture here. [Theory] [InlineData(Besluit.Goedkeuren)] diff --git a/backend/tests/BigRegister.Tests/Domain/IntakeRuleTests.cs b/backend/tests/BigRegister.Tests/Domain/IntakeRuleTests.cs index 2b49b27..2df167f 100644 --- a/backend/tests/BigRegister.Tests/Domain/IntakeRuleTests.cs +++ b/backend/tests/BigRegister.Tests/Domain/IntakeRuleTests.cs @@ -4,7 +4,7 @@ namespace BigRegister.Tests.Domain; public class IntakeRuleTests { - // The arguments ARE the Given (WP-69/bdd.mdx) — these degenerate to When/Then. + // The arguments ARE the Given (bdd.mdx) — these degenerate to When/Then. [Fact] public void Below_threshold_with_no_answer_is_incomplete() => diff --git a/backend/tests/BigRegister.Tests/EndpointTests.cs b/backend/tests/BigRegister.Tests/EndpointTests.cs index cd74ca3..332b578 100644 --- a/backend/tests/BigRegister.Tests/EndpointTests.cs +++ b/backend/tests/BigRegister.Tests/EndpointTests.cs @@ -87,7 +87,7 @@ public class EndpointTests(TestWebApplicationFactory factory) : IClassFixture())!; var submit = await _client.PostAsJsonAsync($"/api/v1/aanvragen/{aanvraag.Id}/submit", @@ -213,7 +213,7 @@ public class EndpointTests(TestWebApplicationFactory factory) : IClassFixture { private readonly HttpClient _client = factory.CreateClient(); diff --git a/backend/tests/BigRegister.Tests/IdempotencyTests.cs b/backend/tests/BigRegister.Tests/IdempotencyTests.cs index d86ba6e..f93aa89 100644 --- a/backend/tests/BigRegister.Tests/IdempotencyTests.cs +++ b/backend/tests/BigRegister.Tests/IdempotencyTests.cs @@ -45,7 +45,7 @@ public class IdempotencyTests(TestWebApplicationFactory factory) : IClassFixture Assert.NotEqual(firstBody!.Referentie, secondBody!.Referentie); } - // RB-18/BIO-018: IdempotencyStore used to key on the raw client-supplied header alone, so + // BIO-018: IdempotencyStore used to key on the raw client-supplied header alone, so // caller B replaying caller A's Idempotency-Key got caller A's cached reference back — // a cross-caller leak of a value caller B never submitted. The store now keys on // "{SubjectId}:{idemKey}", so the same header value from two different callers is two diff --git a/backend/tests/BigRegister.Tests/LetterHtml.golden.html b/backend/tests/BigRegister.Tests/LetterHtml.golden.html index ae16a0a..fbe50c4 100644 --- a/backend/tests/BigRegister.Tests/LetterHtml.golden.html +++ b/backend/tests/BigRegister.Tests/LetterHtml.golden.html @@ -1,7 +1,7 @@ -golden-brief-1