ae7fc1b8f0874e881252181a497358690ea0dff0
144
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ae7fc1b8f0 |
test(acl,e2e): live diploma-upload coverage (refs #103)
- ACL integration test: stores a real document against a published Diploma informatieobjecttype and asserts it is persisted (bestandsnaam, informatieobjecttype, bestandsomvang) and related to the zaak. - e2e: the registration journey now selects a PDF and uploads it (setInputFiles) before the behandelaar step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1abd4b6472 |
feat(portal): real diploma file upload on the self-service page (refs #103)
Replaces the S-10a stub button with a labelled file input (accept application/pdf); the component base64-encodes the chosen file client-side and posts it (with its name and type) keyed by the reference, confirming on success and surfacing a retryable failure. The upload button stays disabled until a file is chosen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d354fe507a |
test(portal): self-service uploads a chosen diploma file (refs #103)
RED: after submitting, the citizen picks a PDF and uploads it; the component base64-
encodes it client-side and posts { contentBase64, fileName, contentType } keyed by the
reference, then confirms. Replaces the S-10a stub button.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
4c516cdad3 |
feat(bff): documents endpoint accepts the base64 file and forwards it to the domain (refs #103)
The self-service documents endpoint takes { contentBase64, fileName, contentType }
as JSON (bsn from the token) and forwards it via IDomainClient.ProvideDocumentsAsync.
Regenerates openapi.json + the Angular client (postSelfServiceRegistrationsIdDocuments
now takes a ProvideDocumentsRequest body).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9d327bbd81 |
test(bff): documents endpoint forwards the base64 file to the domain (refs #103)
RED: the self-service documents endpoint takes the base64 file (+ fileName/contentType) as JSON — the portal encodes client-side — with the bsn from the token, and forwards all of it to the domain. IDomainClient.ProvideDocumentsAsync grows accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3c344caa29 |
feat(domain): ProvideDocuments stores the diploma via the ACL then completes the wait (refs #103)
IAclClient.StoreDiplomaAsync + AclHttpClient (base64 JSON to the ACL /documenten endpoint). ProvideDocuments stores the uploaded bytes against the zaak (when opened) before completing WachtOpDocumenten; the domain endpoint accepts the file base64-encoded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ecad42873c |
test(domain): providing documents stores the diploma via the ACL then completes the wait (refs #103)
RED: ProvideDocuments now carries the file bytes, stores them against the zaak via IAclClient before completing the WachtOpDocumenten wait; owner-scoped; best-effort when no zaak/process exists yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4b4b58b486 |
feat(infra): seed the Diploma informatieobjecttype + wire the ACL default (refs #103)
seed_catalogus.py (OZ_PUBLISH) creates a "Diploma" informatieobjecttype, relates it to the zaaktype (zaaktype-informatieobjecttypen, while both concept), publishes both, and prints INFORMATIEOBJECTTYPE_URL. verify-domain captures it and recreates the ACL with Acl__Defaults__InformatieobjecttypeUrl (placeholder default in compose otherwise). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dca9455bb5 |
feat(acl): store a diploma in the Documenten API and relate it to the zaak (refs #103)
IZaakGateway.StoreDocumentAsync creates an enkelvoudiginformatieobject (base64 inhoud, buffered non-chunked body, no CRS) and relates it via zaakinformatieobjecten; AclService.StoreDiplomaAsync default-fills the ZGW document fields (informatieobjecttype, bronorganisatie, taal nld, creatiedatum); POST /documenten exposes it. Adds the InformatieobjecttypeUrl default (wired in a following infra commit). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
aaa7135fb1 |
test(acl): gateway stores a diploma as an enkelvoudiginformatieobject + relates it (refs #103)
RED: StoreDocumentAsync creates the informatieobject in the Documenten API (base64 inhoud, bestandsomvang, definitief) with a Bearer token and a buffered (non-chunked) body and no CRS headers, then relates it to the zaak via zaakinformatieobjecten, and surfaces an OpenZaak rejection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4777ff2b1d |
feat(workflow): document-wait task + 30-day timeout cancellation (S-10a, closes #102) (#105)
## What & why S-10a, the **workflow/timeout spine** of the (split) document-upload slice: the registratie process now parks at a **`WachtOpDocumenten`** user task with an **interrupting `P30D` boundary timer**. When the documents arrive the task completes and the process continues into the diploma routing (S-13) → Beoordelen; if the 30 days lapse, the timer cancels the wait, runs a `RegistratieVerlopen` external-worker task, and the domain expires the aggregate to a new terminal status **`Verlopen`**. Backend only — the real upload trigger (portal → BFF → ACL → Documenten API) is S-10b (#103). Closes #102 Mechanism recorded in **ADR-0017**; opened as proposal #104. Mirrors the S-14 escalation (boundary-timer + external-worker) and S-11 withdrawal (interrupting cancel) patterns. ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation (red→green pairs per layer). - [x] Implementation makes the test pass. - [x] Conventional Commits referencing the issue (`refs #102`). - [ ] CI green — all Gitea Actions jobs (pending on this PR). - [x] `docker compose up` health unaffected (no new services; deploy path unchanged). - [x] Docs updated (ADR-0017, demo-script, BACKLOG split). - [x] ADR added (`docs/architecture/adr-0017-document-wait-timeout-cancellation.md`). - [x] Demo note in `docs/demo-script.md`. ## Notes for reviewers - **Domain** (`Registration.Expire()` + `Verlopen`), **application** (`ExpireRegistrationWorker`), **infra** (`RegistratieVerlopenProcessor`/`Pump`, `IRegistratieVerlopenClient`, Flowable acquire/complete + `CompleteDocumentWaitAsync`) — the timeout counterpart to the OpenZaak/escalation worker trios; idempotent per §8.6. - **BPMN** verified live against a `flowable-rest` probe: complete `WachtOpDocumenten` → routes to Beoordelen; fire the P30D timer → `RegistratieVerlopen` job (carrying `registrationId`) + the wait task cancelled. `verify-domain` exercises both branches in-stack (completes the wait in every existing block; fires the timer and asserts `Verlopen` in a new block). - **Scope boundary:** on expiry the aggregate goes `Verlopen` and the process ends, but the ZGW *zaak* is not yet set to a cancellation status — that needs a new ACL method + statustype seeding and is folded into S-10b (noted in ADR-0017). - `CompleteDocumentWaitAsync` is built and HTTP-tested here but not yet called from a domain endpoint; S-10b wires the upload trigger to it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #105 |
||
|
|
ccae27b3da |
feat(workflow): diploma-eligibility DMN routes foreign diplomas via CBGV-advies (S-13, closes #14) (#101)
## What & why S-13: a diploma's origin decides its route. A **DMN** (`diploma-eligibility`) is evaluated inline by the registratie process as a **`businessRuleTask`**; an exclusive gateway routes a **foreign** (Buitenlands) diploma through a new **CBGVAdvies** user task before `Beoordelen`, a **domestic** one straight there (PRD flow 4). The domain's only new job is carrying the diploma origin and passing it as a process start variable. Chose **Option B (DMN in the BPMN)** over the issue's literal "evaluated by the Domain Service via Workflow Client" wording — keeps the decision a first-class workflow artefact and §8.2 clean. Rationale in **ADR-0016** (proposal #100); noted on this issue. Closes #14 ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation. - [x] Implementation makes the test pass. - [x] Conventional Commits referencing the issue (`refs #14`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes (additive; DMN deployed by flowable-init). - [x] Docs updated (ADR-0016, demo note). - [x] ADR added (`docs/architecture/adr-0016-diploma-eligibility-dmn.md`). - [x] Demo note in `docs/demo-script.md`. ## How it was built (TDD) - **Domain**: `DiplomaOrigin` on the aggregate + submit command; threaded through the process-start port so the Workflow Client emits a `diplomaOrigin` start variable. Red → green. - **DMN + BPMN**: `workflows/diploma-eligibility.dmn` (origin → route); `businessRuleTask` + exclusive gateway + `CBGVAdvies` user task in `registratie.bpmn`; DMN deployed to Flowable's DMN engine by `flowable-init`. - **Both paths**: `Een diploma op herkomst routeren` acceptance scenarios (origin carried into the process) + unit tests; verify-domain drives a foreign registration through CBGVAdvies→Beoordelen and the domestic one straight to Beoordelen — exercising both DMN branches live. ## Notes for reviewers - Deviation from the issue's Option-A wording is deliberate and recorded (ADR-0016); the outcome is unchanged. - The self-service eIDAS→foreign wiring is out of scope here (this slice is area:domain + area:workflow); the domain submit accepts an optional `diplomaOrigin` so the foreign path is drivable. - Local green: domain unit 109, acceptance 15, `dotnet format`, Release build (0 errors), **domain mutation 95.39%** (break 90). The DMN/`businessRuleTask` REST wiring is CI-verified on verify-stack (no local full-stack run here). Reviewed-on: #101 |
||
|
|
7bcbc726ce |
feat(workflow): beoordeling escalation to teamlead after 14 days (S-14, closes #15) (#99)
## What & why S-14: a beoordeling a behandelaar does not pick up within **14 days** escalates to the **teamlead**. A non-interrupting `P14D` boundary timer on the `Beoordelen` user task fires an external-worker task (`BeoordelingEscaleren`); the domain's escalation worker reassigns the still-open task's candidate group from `behandelaar` to `teamlead`. The task keeps its identity — only who may claim it changes. The escalation-via-external-worker decision is recorded in **ADR-0015** (proposal #98); it upholds §8.2 (the Workflow Client stays the only code that talks to Flowable) and keeps Flowable a stock image. Closes #15 ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation. - [x] Implementation makes the test pass; refactor commit if structure improved. - [x] Conventional Commits referencing the issue (`refs #NN`). - [x] CI green — all Gitea Actions jobs. - [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes (no new services; escalation is additive to the domain worker). - [x] Docs updated (ADR-0015, demo note). - [x] ADR added (`docs/architecture/adr-0015-beoordeling-escalation.md`). - [x] Demo note in `docs/demo-script.md`. ## How it was built (TDD) - **Workflow Client** (`IBeoordelingEscalatieClient`): acquire `BeoordelingEscaleren` jobs → find the open `Beoordelen` task in the instance → add `teamlead`/remove `behandelaar` candidate group → complete the job. Red → green. - **Escalation drain loop** (`BeoordelingEscalatieProcessor`) + hosted `BeoordelingEscalatiePump`, mirroring the OpenZaak worker. Red → green. - **BPMN**: non-interrupting `P14D` boundary timer on `Beoordelen` → external task → escalation end. - **Both branches** (escalate after timeout; no-op when completed in time) covered by the `Een beoordeling escaleren` acceptance scenarios + Workflow Client unit tests. - **Live integration**: `verify-domain` fires the timer early via Flowable's management API and asserts the reassignment to teamlead. ## Notes for reviewers - Interface segregation: escalation is on `IBeoordelingEscalatieClient`, separate from the OpenZaak worker's `IExternalWorkerClient`. - Reassignment is two REST hops (add teamlead, remove behandelaar); idempotent on redelivery — see ADR-0015 consequences. - Local checks green: domain unit tests (104), acceptance (13), `dotnet format --verify-no-changes`, Release build (0 errors), **domain mutation 96.69%** (break 90). The `run-domain-check.sh` escalation path is CI-verified on verify-stack (local full-stack run is constrained here). - `BeoordelingEscalatiePump` excluded from mutation, mirroring the existing `OpenZaakJobPump` exclusion. Reviewed-on: #99 |
||
|
|
8a537edd6c |
fix(infra): engine-portable portal nginx resolver (closes #96) (#97)
## What & why Closes #96. The portal nginx configs hardcode `resolver 127.0.0.11` (Docker's embedded DNS) for their variable `proxy_pass` to the BFF, so on rootless **podman** (network-specific aardvark DNS) every proxied call 502'd — the portals loaded and login worked, but no in-app data flowed. Add a shared `/docker-entrypoint.d` hook (`apps/portal-nginx-resolver.sh`, wired into all three portal Dockerfiles) that rewrites the resolver from the container's own `/etc/resolv.conf` at startup: a **no-op on Docker** (nameserver *is* 127.0.0.11) and **correct on podman** (rewrites to e.g. 10.89.0.1). nginx.conf is unchanged (the hardcoded value is the substitution anchor). ## How verified Built the behandel image and ran it on the compose network under podman: the hook rewrote the config to `resolver 10.89.0.1`, and `GET /behandel/werkbak` proxied to the BFF returning **401** (auth), not 502. On Docker the nameserver is 127.0.0.11 so the substitution is a no-op and CI/e2e behaviour is unchanged. Reviewed-on: #97 |
||
|
|
e7bed37cda |
fix(infra): local event-subscriber Acl:BaseUrl parity (closes #94) (#95)
## What & why Closes #94. The local compose's `event-subscriber` lacked `Acl__BaseUrl` (and the `acl` dependency) that the canonical compose sets (#78) — so it threw `Missing configuration 'Acl:BaseUrl'` and exited on startup, which also knocked over podman-compose's bring-up of the rest of the stack (the frontends were left uncreated). Adds the env + dependency, matching `docker-compose.yml`. ## How verified Recreated `event-subscriber` from the fixed compose locally — it now starts healthy, and the three portals come up (self-service :8140, openbaar :8141, behandel :8142). `docker compose config` valid. ## Note (separate, not fixed here) On **rootless podman** the portal→BFF nginx proxy still 502s (`resolver 127.0.0.11` is Docker's embedded DNS; podman uses its own), and podman-compose orchestration of this dependency graph is flaky — both are pre-existing local-engine limitations, clean on Docker Desktop / CI. Tracking separately. Reviewed-on: #95 |
||
|
|
94699f3603 |
feat(self-service): trek aanvraag in — withdrawal action (S-11c-2, closes #12) (#93)
## What & why Final sub-slice of **S-11 · Withdrawal (Flow 3)** — the user-facing "trek aanvraag in" action, which **closes #12**. - **self-service portal**: the submit confirmation gains a **"Trek aanvraag in"** button. It withdraws the just-submitted registration via `postSelfServiceRegistrationsIdWithdraw(reference)`; success shows an *ingetrokken* confirmation, a failure is surfaced (`role="alert"`) and the action stays available — same confirm-and-surface pattern as submit. - **acceptance**: `Een registratie intrekken` — owner withdraws → INGETROKKEN + workflow cancelled; a different bsn is reported not-found. - **e2e**: `withdrawal.spec.ts` — DigiD submit → trek aanvraag in → the portal confirms ingetrokken. - **docs**: demo-script + frontend-decisions. Together with S-11a (#88), S-11b (#89), S-11c-1 (#90), this completes the flow: citizen withdraws → domain INGETROKKEN → BPMN message event cancels the process → the case leaves the behandelaar's werkbak. Closes #12 ## Definition of Done - [x] Linked Gitea issue (#12). - [x] Failing tests committed before the implementation. - [x] Implementation makes the tests pass. - [x] Conventional Commits referencing the issue (`refs #12`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` unaffected. - [x] Docs updated (demo-script + frontend-decisions). - [x] ADR — ADR-0014 (from S-11b) covers the cancellation decision; nothing new here. ## Notes for reviewers - Full local gate run before pushing: `dotnet format --verify-no-changes` clean; `make unit` green (Acceptance **11** incl. the 2 new withdrawal scenarios, Big 95, BFF 30, Acl 27, EventSubscriber 19); self-service lint/test/build green (9 tests, incl. the 2 new withdraw tests). - `withdrawal.spec.ts` waits on the *ingetrokken* confirmation (which only renders after the withdraw POST returns), so it can't cancel the request early (the 499 lesson from #87). Live-validated by verify-stack. Reviewed-on: #93 |
||
|
|
951bdd8364 |
fix(infra): local compose parity + host-browser OIDC (closes #91) (#92)
## What & why Closes #91. `infra/docker-compose.local.yml` (the no-make local stack) was missing the `domain` service and all three portals, and never wired host-browser OIDC — so browsing the behandel portal redirected to `http://keycloak:8080/…`, which a host browser can't resolve. - **Parity**: add `domain`, `self-service`, `openbaar`, `behandel` (local now matches the CI-canonical `docker-compose.yml` service-for-service). - **BFF**: give it the Keycloak + downstream env it was missing (it previously fell back to appsettings and couldn't reach Keycloak). - **Host-browser OIDC**: pin Keycloak's frontend/issuer URL to `http://localhost:8180` (`KC_HOSTNAME`) with `KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true`, so a host browser logs in on `localhost:8180` while the BFF still validates in-network via `keycloak:8080`. - **Portals**: bind-mount a `localhost:8180` `config.json` over the image's baked `keycloak:8080` one (`infra/local-config/*`). openbaar is anonymous, no config. ## How verified - `docker compose -f infra/docker-compose.local.yml config` valid; parity check shows nothing missing. - Started Keycloak from the local compose and confirmed the discovery document: - **host view** (`localhost:8180`): `issuer` + all endpoints on `localhost:8180` (what the browser uses). - **in-network view** (`keycloak:8080`): `issuer` stays `http://localhost:8180/...` (matches browser tokens) while `jwks_uri`/`token_endpoint` resolve to `keycloak:8080` (reachable by the BFF). ## Notes for reviewers - The full portal→BFF→Keycloak login round-trip should get a quick browser smoke test on a real engine (I validated the Keycloak issuer/backchannel split and compose validity, but can't drive a browser here). Ports: self-service :8140, openbaar :8141, behandel :8142; users in `docs/synthetic-data.md`. - On rootless podman the portal→BFF nginx proxy (`resolver 127.0.0.11`) may 502 (a separate known podman-vs-docker DNS quirk); login is a browser redirect and is unaffected. Works on Docker Desktop. - No app-code change; `docker-compose.yml` (CI-canonical) is untouched. Reviewed-on: #92 |
||
|
|
2397d9196a |
feat(bff): owner-scoped self-service withdraw endpoint (S-11c-1, refs #12) (#90)
## What & why Third sub-slice of **S-11 · Withdrawal (Flow 3)** (#12) — the **owner-scoped BFF withdraw endpoint** (backend). S-11a/b made a withdrawal transition the aggregate and cancel the workflow; this adds the citizen-facing entry point through the BFF, gated to the registration's owner. - **Domain**: `WithdrawRegistrationCommand` carries the caller's `bsn`; the handler returns a `WithdrawOutcome` and refuses a bsn that doesn't own the registration. Unknown and not-owned are **both 404** (indistinguishable — ownership isn't revealed). `POST /registrations/{id}/withdraw` takes `{bsn}` and maps the outcome (204/404). - **BFF**: `POST /self-service/registrations/{id}/withdraw` (DigiD-authenticated) forwards the token's `bsn` to the domain and relays 204/404. The BFF authenticates; the domain owner-scopes (an aggregate invariant, not the domain doing auth). - OpenAPI spec + Angular client regenerated for the new endpoint. - `run-domain-check.sh` withdrawal step now sends the owner `bsn` (verify-stack). Refs #12 — the self-service "trek aanvraag in" button + e2e (S-11c-2) closes it. ## Definition of Done - [x] Linked Gitea issue (#12). - [x] Failing tests committed before the implementation. - [x] Implementation makes the tests pass. - [x] Conventional Commits referencing the issue (`refs #12`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` unaffected. - [x] No ADR needed (owner-scoping is an aggregate invariant; no boundary change). - [x] Docs — the user-visible demo note lands with S-11c-2. ## Notes for reviewers - **Full local gate run before pushing this time** (lessons from #89): `dotnet format --verify-no-changes` clean; `make unit` green — Acl 27, EventSubscriber 19, BFF 30, Acceptance 9, Big 95; `api-client` lint+test green. - Owner mismatch returns 404 (not 403) so the portal can't be used to probe which references exist. Reviewed-on: #90 |
||
|
|
a34caba9ea |
feat(domain): withdrawal cancels the registratie process (S-11b, refs #12) (#89)
## What & why Second sub-slice of **S-11 · Withdrawal (Flow 3)** (#12). S-11a (#88) made a withdrawal advance the aggregate to INGETROKKEN; this sub-slice **cancels the running Flowable process** so the withdrawn case leaves the behandelaar's werkbak. - **BPMN** (`registratie.bpmn`): an interrupting message boundary event (`RegistratieIngetrokken`) on the `Beoordelen` task, routing to a dedicated "Registratie ingetrokken" end event. - **Workflow Client**: `WithdrawBeoordelingAsync(executionId)` delivers `messageEventReceived` to the task's execution (PUT); `BeoordelingTask` now carries its `executionId`. - **`WithdrawRegistration` handler**: after the domain transition, finds the open `Beoordelen` task for the registration and delivers the withdrawal message — best-effort, mirroring how the beoordeling completes its task. - **Werkbak**: also filters out registrations that are no longer open, so a withdrawn case never surfaces even in the brief window before cancellation lands. - **ADR-0014** records the decision (message event in BPMN vs. deleting the instance from code). - **verify (`run-domain-check.sh`)**: a second registration parks at `Beoordelen`, is withdrawn via the domain, and the check asserts its `Beoordelen` task disappears — so verify-stack validates the live Flowable message correlation. Refs #12 (S-11c — the BFF + self-service "trek aanvraag in" button + e2e — closes it). ## Definition of Done - [x] Linked Gitea issue (#12). - [x] Failing tests committed before the implementation (red → green per commit). - [x] Implementation makes the tests pass. - [x] Conventional Commits referencing the issue (`refs #12`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` unaffected (BPMN redeploys on a fresh CI DB via flowable-init). - [x] ADR added (ADR-0014). - [x] Docs — the user-visible demo note lands with S-11c. ## Notes for reviewers - Verified locally: `Big.Tests` 94/94 pass; `Big.Api` builds; `registratie.bpmn` is well-formed. - The Flowable message-correlation REST shape is validated **live** by verify-stack (the Workflow Client unit tests stub the exchange and assert only the request shape, per ADR-0009) — the new `run-domain-check.sh` withdrawal step is that live check. - Known gap (ADR-0014): a withdrawal that races ahead of the process reaching `Beoordelen` finds no task to cancel; the aggregate is still INGETROKKEN and the werkbak filter hides it, but that instance parks unattended. A process-level event subprocess would close the gap — deferred. Reviewed-on: #89 |
||
|
|
1f1c944a8b |
feat(domain): withdrawal — INGETROKKEN transition + endpoint (S-11a, refs #12) (#88)
## What & why First sub-slice of **S-11 · Withdrawal (Flow 3)** (#12). A zorgprofessional can withdraw a still-open registration ("trek aanvraag in"); this sub-slice delivers the **domain transition + endpoint**, mirroring how S-12a shipped the beoordeling decision model on its own (#82). - `RegistrationStatus.Ingetrokken` (terminal). - `Registration.Withdraw()` — allowed from INGEDIEND or IN_BEHANDELING, needs no zaak, idempotent, and rejected once the registration has been decided (INGESCHREVEN/AFGEWEZEN). - `WithdrawRegistration` application handler (load → withdraw → persist; repeated withdrawal is a no-op). - `POST /registrations/{id}/withdraw` on the domain API. Demoable: `POST /registrations/{id}/withdraw` → `GET /registrations/{id}` shows `INGETROKKEN`. Refs #12 (not closing — see below). ## Scope / follow-ups S-11 is bigger than one slice, so it is split (CLAUDE.md §13), like S-12 was: - **S-11a (this PR)** — domain withdrawal transition + endpoint. - **S-11b** — cancel the running Flowable process via a BPMN message event, so a withdrawn case leaves the behandelaar's werkbak. - **S-11c** — owner-scoped BFF self-service withdraw endpoint + "trek aanvraag in" button + e2e. Cancelling the Flowable process is deliberately deferred (documented in `WithdrawRegistration`), exactly as the beoordeling's rejection deferred its zaak propagation. #12 stays open until S-11c. ## Definition of Done - [x] Linked Gitea issue (#12). - [x] Failing test committed before the implementation. - [x] Implementation makes the test pass. - [x] Conventional Commits referencing the issue (`refs #12`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` unaffected (no infra/contract change). - [x] Docs — none needed for this backend sub-slice; the user-visible demo note lands with S-11c. - [x] No ADR needed — mirrors existing aggregate/handler/endpoint patterns; no boundary change. ## Notes for reviewers - Verified locally: `Big.Tests` 89/89 pass; `Big.Api` builds clean. - The domain trusts its callers (§8.3); owner-scoping by the caller's bsn is enforced at the BFF in S-11c. Reviewed-on: #88 |
||
|
|
3abf8f7ccf |
feat(behandel): behandel-portal — werkbak + beoordeling (closes #13) (#87)
## What & why
Finishes **S-12 · Behandel-portal — werkbak + beoordeling**. The backend sub-slices (S-12a/b/c-1/c-2) were merged, but the slice's stated outcome — a behandel *portal* with medewerker login, a werkbak, and decide — had no frontend. This adds it.
- **`libs/auth`**: `MedewerkerAuthService` + `provideMedewerkerAuth` (Keycloak `medewerker` realm), a `roles`/`hasRole` surface on the shared `AuthService`, and a realm-roles protocol mapper so the SPA can read `behandelaar`/`teamlead` from the token. The BFF remains the security boundary (ADR-0013).
- **`apps/behandel`**: a new Nx Angular app mirroring self-service — medewerker OIDC login and a **werkbak** page listing registrations awaiting beoordeling (`GET /behandel/werkbak`) with per-row **Goedkeuren/Afwijzen** actions (`POST /behandel/registrations/{id}/decide`) that refresh the list. NL DS/Utrecht, standalone + signals.
- **e2e**: the walking-skeleton happy path now approves through the real portal (behandelaar logs in, finds the row by reference, clicks Goedkeuren) instead of the temporary admin endpoint.
- **infra/docs**: behandel service in compose (`:8142`, depends on Keycloak); added to the smoke `WAIT_SVCS` + CI log dump; `frontend-decisions.md` and `demo-script.md` updated.
Closes #13
## Definition of Done
- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation.
- [x] Implementation makes the test pass; refactor commit if structure improved.
- [x] Conventional Commits referencing the issue (`refs #13`).
- [ ] CI green — all Gitea Actions jobs.
- [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes. *(behandel image + container verified locally; full stack gated in CI.)*
- [x] Docs updated if behaviour, contracts, or operations changed.
- [x] ADR added — ADR-0013 (merged with the backend sub-slices) already covers the wiring; no new decision here.
- [x] Demo note in `docs/demo-script.md`.
## Notes for reviewers
- Verified locally: auth + behandel + all frontend projects pass lint & unit tests (incl. axe WCAG 2.1 AA); production build green; the behandel Docker image builds and serves with the correct baked `medewerker` config + SPA fallback.
- The full compose-up smoke, e2e, and mutation are CI-gated (known local full-stack verify limits).
- **Follow-ups (not in scope):** the `WerkbakItem` contract has no citizen name (werkbak shows the BSN) — adding one is a BFF+domain contract change; and the domain's temporary admin `approve` endpoint is now unused by the e2e and could be removed.
Reviewed-on: #87
|
||
|
|
d226b6402d |
feat(#13): S-12c-2 — behandel decide → domain + complete workflow task (#86)
## What & why
Second half of **S-12c** (behandel-portal backend), completing the decision path per **ADR-0013**:
- **Domain:** `BeoordeelRegistratie` now, after applying the decision (aggregate + ACL for approval), **completes the open Flowable `Beoordelen` task** for that registration (found by registrationId) with the besluit, so the workflow advances. No open task → the decision still stands (completes nothing); idempotent.
- **BFF:** `POST /behandel/registrations/{id}/decide` behind the medewerker/`behandelaar` policy, forwarding `goedkeuren`/`afwijzen` to the domain. Validates the besluit vocabulary (400 on unknown) without troubling the domain.
Behavior: decide is **401** without a token, **403** without the role, **400** for an unknown besluit, **204** (forwarded) for a behandelaar.
This completes the behandel backend. **S-12d** (the Angular behandel-portal + Playwright e2e) closes umbrella #13 and retires the temporary `/approve`.
## Definition of Done
- [x] Linked issue: #13 (umbrella, `refs`)
- [x] Tests first; red → green per layer
- [x] Unit + acceptance green (`make unit`): domain 79, bff 27, acceptance 9 (acl/event-subscriber unaffected)
- [x] Beoordeling acceptance scenario asserts task completion (goedkeuren + afwijzen)
- [x] openapi.json + api-client regenerated (drift guard passes)
- [x] Mutation ≥ break(90): **domain 100%, bff 100%**
- [ ] CI green (pending)
Part of #13.
Reviewed-on: #86
|
||
|
|
9c3da48d8e |
feat(#13): S-12c-1 — behandel BFF auth + werkbak (ADR-0013) (#85)
## What & why First half of **S-12c** (behandel-portal backend), per **ADR-0013** (decisions recorded in #84): - **BFF multi-realm auth.** A second JWT bearer scheme (`medewerker`) alongside the default `digid` scheme. On validation it lifts Keycloak's `realm_access.roles` onto the principal, and a `behandelaar` policy (medewerker scheme + `behandelaar` role) gates `/behandel/*`. Self-service keeps the digid scheme. - **Werkbak = Flowable tasks.** The domain `Werkbak` query reads the open `Beoordelen` tasks (§8.2, S-12b's `IUserTaskClient`) and enriches each with its aggregate's bsn + status; `GET /behandel/werkbak` (domain) is proxied by the BFF `GET /behandel/werkbak` behind the behandelaar policy. The read projection stays the anonymous openbaar model (no premature `IN_BEHANDELING`/personal-data plumbing — deferred in ADR-0008). Behavior: `/behandel/werkbak` is **401** without a token, **403** for a medewerker lacking the role, **200 + werkbak** for a behandelaar. **S-12c-2** (next): `POST /behandel/registrations/{id}/decide` → domain decision + complete the Flowable task. ## Definition of Done - [x] Linked issue: #13 (umbrella, `refs`); closes the adr-proposal #84 - [x] Tests first; red → green per layer - [x] Unit + acceptance green (`make unit`): domain 78, bff 23, acceptance 9 (+ acl/event-subscriber unaffected) - [x] api-client `test` green; openapi.json regenerated (drift guard passes) - [x] Mutation ≥ break(90): **domain 100%, bff 100%** - [x] ADR-0013 added; `Keycloak__MedewerkerAuthority` wired into compose - [ ] CI green (pending) Part of #13. closes #84 Reviewed-on: #85 |
||
|
|
4085bdead7 |
feat(#13): S-12b — Workflow Client user-tasks + Beoordelen userTask (#83)
## What & why Second sub-slice of **S-12 (#13)** — the **Workflow Client gains behandelaar user-task operations**, and the process model gains the beoordeling step. - **BPMN:** `registratie.bpmn` now parks at a `Beoordelen` **userTask** (candidate group `behandelaar`) after `OpenZaakAanmaken`; `registrationId` rides along as a process variable so the werkbak can correlate each task to its aggregate. - **Workflow Client** (`IUserTaskClient`, the only code that talks to Flowable §8.2): - `GetOpenBeoordelingenAsync()` — the werkbak (open `Beoordelen` tasks + their `registrationId`) - `ClaimAsync(taskId, behandelaar)` - `CompleteBeoordelingAsync(taskId, besluit)` — carries the decision into the process as the `besluit` variable - **Live integration:** `verify-domain` now drives the full user-task lifecycle against a real Flowable — after the worker opens the zaak, it polls for the task, claims it as `merel-behandelaar`, completes it (`goedkeuren`), and asserts the process finishes. This proves the exact REST contract (`service/runtime/tasks/query` + `…/{id}` claim/complete) the client depends on. The walking skeleton is unaffected: the temporary `/approve` path still sets the zaak status directly; wiring the domain decision to *complete this task* (and driving the werkbak from the BFF) lands in **S-12c**. ## Definition of Done - [x] Linked issue: #13 (umbrella; `refs`, does not close) - [x] Tests first; red → green - [x] Unit + acceptance green (`make unit`): domain 76, acceptance 9 (acl/event-subscriber/bff unaffected) - [x] Mutation ≥ break(90): **domain 100%** (killed the new survivors *and* the pre-existing `FlowableWorkflowClient` baseline) - [x] Live Flowable user-task lifecycle asserted in `verify-domain` - [ ] CI green (pending) Part of #13. Reviewed-on: #83 |
||
|
|
d4ed0ffc22 |
feat(#13): S-12a — beoordeling decision model (domain) (#82)
## What & why First sub-slice of **S-12 (#13)** — the **beoordeling decision model** in the Domain Service. Foundation for the behandel-portal: it gives the domain a proper decision lifecycle before any UI/Flowable/BFF work. - **Statuses:** add `InBehandeling` and `Afgewezen` to `RegistrationStatus`. - **Aggregate:** `TakeIntoBehandeling()` (`Ingediend → InBehandeling`, idempotent, guards terminal states); generalise the behandelaar decision — `Approve()` (requires a zaak) and new `Reject()` both act on an `Ingediend`/`InBehandeling` registration → `Ingeschreven`/`Afgewezen`. - **Use-case:** `BeoordeelRegistratie` (`goedkeuren` sets the zaak's final status via the ACL §8.1 → `Ingeschreven`; `afwijzen` → `Afgewezen`, domain-only for now). Idempotent. - **Endpoint:** `POST /registrations/{id}/decide` (`{ "besluit": "goedkeuren" | "afwijzen" }`), superseding the temporary `/approve` (retired when the portal lands, S-12d). - **BDD:** `EenRegistratieBeoordelen.feature` — goedkeuren + afwijzen scenarios (feature-scoped bindings). **Scoped out** to later S-12 sub-slices: Flowable user-task claim/complete + BPMN `userTask` (S-12b), BFF `/behandel/*` + medewerker authz (S-12c), the Angular behandel-portal + e2e (S-12d), and propagating a *rejection* to the zaak/projection via the ACL. ## Definition of Done - [x] Linked issue: #13 (umbrella; this PR `refs`, does not close) - [x] Tests first; red → green per behaviour - [x] Unit + acceptance green (`make unit`): domain 65, acceptance 9 - [x] Mutation ≥ break(90): domain 98.77%, no survivors in new code (the one unkilled mutant is the pre-existing `FlowableWorkflowClient` baseline) - [ ] CI green (pending) Part of #13. Reviewed-on: #82 |
||
|
|
3023bb6fbe |
chore(release): 2026.07.0 (#81)
Cuts the first CalVer release **2026.07.0** (tag ), marking the end of **Iteration 1 — Walking Skeleton**. regenerated from Conventional Commits by git-cliff (covers Iterations 0 and 1, through #79). After merge: tag on main and publish the Gitea Release. closes #80 Reviewed-on: #81v2026.07.0 |
||
|
|
9997da8beb |
feat(#78): one citizen reference across self-service and the openbaar register (#79)
## What & why Before this change the self-service confirmation and the openbaar register showed **different** identifiers, so a citizen could not look their registration back up (#78). Now both surface the same **reference**: - **domain → ACL (write):** the domain `registrationId` is set as the zaak's `identificatie` on `POST /zaken`. - **event-subscriber → ACL (read):** the subscriber reads the zaak's `identificatie` back through the ACL (§8.1 — only the ACL talks to ZGW) via a new `POST /zaken/reference`, and stores it on the projection row **and** the `processed_notifications` replay log. - **BFF + openbaar:** the public view exposes `id/status/reference` (never bsn/naam) and searches by id or reference; the register's "Referentie" column shows the reference. Storing the reference in the replay log keeps ADR-0008's **rebuild-is-log-only** invariant intact — `/admin/rebuild` reproduces the reference without re-reading the ACL. Decision recorded in **ADR-0012**. ## Definition of Done - [x] Linked issue: #78 - [x] Tests written first; red → green per layer - [x] Unit + acceptance green (`make unit`): domain 49, acl 27, bff 20, event-subscriber 19, acceptance 7 - [x] Frontend lint + test green (`nx run-many -t lint test`) - [x] Mutation ≥ break(90): acl 100%, event-subscriber 100%, bff 100%, domain 98.41% (pre-existing FlowableWorkflowClient baseline, untouched) - [x] e2e extended: confirmation reference == register reference - [x] openapi.json + api-client regenerated (drift guard green) - [x] ADR-0012 added; demo-script note appended - [x] `Acl__BaseUrl` wired for the subscriber in compose closes #78 Reviewed-on: #79 |
||
|
|
1c185e6686 |
S-09b: Approval flow — temp admin endpoint + status transition to projection (#77)
## What & why S-09b (#75, split from #10) — the **approval flow** that completes the walking skeleton. A behandelaar can now approve a submitted registration; the entry flips from `INGEDIEND` to `INGESCHREVEN` in the public register. Flow: `POST /registrations/{id}/approve` (domain) → ACL sets the zaak eindstatus (ZGW `/statussen`) → OpenZaak → NRC → event-subscriber → projection → openbaar. ## Changes (bottom-up, each red→green TDD) - **Domain** — `RegistrationStatus.Ingeschreven` + `Registration.Approve()` (guards: opened zaak, only from INGEDIEND); `ApproveRegistration` use case (idempotent) + temp `POST /registrations/{id}/approve` endpoint; `IAclClient.ApproveZaakAsync`. - **ACL** — resolves the zaaktype's **eindstatus** from the catalogus (`isEindstatus` / highest volgnummer) and POSTs a ZGW status; exposed as `POST /statussen`. Unit + real-OpenZaak integration test. - **Event-subscriber** — binds NRC `hoofdObject`, projects a `status`/`create` as `INGESCHREVEN` keyed on the zaak (updates the existing row), **without reading OpenZaak** (§8.1). Retains the ZGW `resource` in the log (new column + EF migration) so a rebuild reproduces the status. - **e2e** — extended: submit → public INGEDIEND → approve → public INGESCHREVEN. - **Docs** — ADR-0011 (the two non-obvious decisions + the walking-skeleton assumption) + demo note. ## Key decisions (see ADR-0011) - **ACL discovers the eindstatus** (chosen over injecting a statustype URL): no new config/seed plumbing, domain stays ZGW-ignorant. - **Any post-creation status-set ⇒ INGESCHREVEN**: in the walking skeleton the only status ever set after creation is the approval, and the subscriber may not read ZGW — documented to tighten when more transitions arrive (S-12+). ## Verification - All .NET unit suites green locally (domain 47, acl 11, event-subscriber 14, bff 16, acceptance 7); Release build + `dotnet format` clean. - No new compose config (the eindstatus-discovery approach avoided it). - The real-OpenZaak integration test (ACL status-set) and the full submit→approve→visible e2e run in CI `verify-stack` (live NRC→projection + selectielijst egress, not reproducible locally). closes #75 Reviewed-on: #77 |
||
|
|
bc9831c113 |
S-09: Openbaar Register portal — public lookup (#76)
Anonymous openbaar portal completing the walking skeleton (submit → projection → public visibility). closes #10 |
||
|
|
7e8c5d7b51 |
Merge pull request 'ci: speed up pipeline — NuGet cache + prebuilt Playwright image' (#74) from chore/73-ci-speedups into main
Reviewed-on: #74 |
||
|
|
2b9eb5eb41 |
ci(e2e): run Playwright from the prebuilt image instead of downloading browsers (refs #73)
CI / build (pull_request) Successful in 56s
CI / mutation (pull_request) Successful in 4m6s
CI / verify-stack (pull_request) Successful in 7m3s
CI / unit (pull_request) Successful in 1m0s
CI / frontend (pull_request) Successful in 1m50s
CI / lint (pull_request) Successful in 5m43s
The verify-e2e lane downloaded ~150 MB of Chromium (npx playwright install) on every verify-stack run. Use the official mcr.microsoft.com/playwright image with browsers pre-baked; npm install still pins @playwright/test from tests/e2e, and the image tag is kept in lockstep with that version. Verified the exact create + docker cp + start flow launches the baked browser with no download. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
60df0845aa |
ci: cache the NuGet package store across the .NET jobs (refs #73)
lint, build, unit and mutation each restored packages from the network on every run. There are no lock files (so setup-dotnet's built-in cache doesn't apply), so cache ~/.nuget/packages keyed on the project files via actions/cache. Pinned @v3 to avoid the GHES guard that breaks @v4 on Gitea (gitea-actions-gotchas.md); the cache is best-effort, so a miss simply restores from the network. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2a746736dc |
Merge pull request 'test(e2e): serve the portal + walking-skeleton Playwright e2e (closes #68)' (#72) from feat/68-e2e into main
Reviewed-on: #72 |
||
|
|
986e36bc7d |
test(portal-self-service): guard that the DigiD token attaches to relative BFF calls (refs #68)
CI / lint (pull_request) Successful in 1m8s
CI / build (pull_request) Successful in 53s
CI / unit (pull_request) Successful in 58s
CI / frontend (pull_request) Successful in 2m10s
CI / mutation (pull_request) Successful in 3m58s
CI / verify-stack (pull_request) Successful in 7m48s
The token-attachment bug (secureRoutes set to the app origin, which a relative api-client URL never matches) was only caught by the full-stack e2e. Add a fast unit guard: drive the REAL angular-auth-oidc-client interceptor and the REAL api-client against the production route value, faking only the config source and the token storage. Asserts the bearer token rides the relative /self-service/ call and is withheld from the anonymous /openbaar/ call. Extract the value to a shared SECURE_API_ROUTES constant so the test binds to exactly what the app configures. Verified the guard fails (Authorization null) if the value regresses to an origin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7e152e4432 |
feat(portal-self-service): surface submit failures with a retryable alert (refs #68)
CI / build (pull_request) Successful in 54s
CI / lint (pull_request) Successful in 1m11s
CI / unit (pull_request) Successful in 1m0s
CI / frontend (pull_request) Successful in 1m49s
CI / mutation (pull_request) Successful in 4m3s
CI / verify-stack (pull_request) Successful in 7m42s
Add an error branch to submit(): on a failed BFF call, set a `failed` signal, re-enable the button, and render a role="alert" message so the user knows the submit did not go through and can retry — instead of the click silently doing nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5bf25f094d |
test(portal-self-service): submit surfaces BFF failures instead of swallowing them (refs #68)
Failing test: when postSelfServiceRegistrations errors, the page should show an alert, not the confirmation, and keep the submit button available for retry. Currently submit() has no error handler, so the rejection is swallowed and the page silently stays put — exactly the failure mode that hid the missing-token bug behind a 90s e2e timeout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0e6c7d2066 |
fix(portal-self-service): attach the DigiD token to relative BFF calls (refs #68)
CI / lint (pull_request) Successful in 1m12s
CI / build (pull_request) Successful in 51s
CI / unit (pull_request) Successful in 1m3s
CI / verify-stack (pull_request) Successful in 7m59s
CI / frontend (pull_request) Successful in 1m48s
CI / mutation (pull_request) Successful in 3m57s
After login the submit silently did nothing: the confirmation ("...is
ontvangen...") never rendered because the POST to the BFF went out with no
Authorization header, so the BFF rejected it and the no-error-handler
subscribe left the page unchanged.
Root cause: angular-auth-oidc-client's interceptor attaches the token when
`req.url.startsWith(secureRoute)`. The api-client calls the BFF with RELATIVE
URLs (same-origin via the nginx proxy), so `req.url` is `/self-service/...` —
but secureRoutes was configured as the app ORIGIN (`http://self-service`),
which a relative URL never starts with. No match → no token.
Configure secureRoutes with the relative `/self-service/` prefix instead. The
unit test mocked the api-client, so only the walking-skeleton e2e exercises the
real token attachment — now green.
Verified against a focused stack (keycloak + self-service + real BFF + stub
domain): the submit now carries the bearer token, the BFF forwards to the
domain, and the portal shows the confirmation with the returned reference.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
39923e0e68 |
fix(e2e): treat the http portal origin as secure so DigiD PKCE login works (refs #68)
CI / lint (pull_request) Successful in 1m12s
CI / build (pull_request) Successful in 53s
CI / unit (pull_request) Successful in 1m3s
CI / frontend (pull_request) Successful in 1m47s
CI / mutation (pull_request) Successful in 4m2s
CI / verify-stack (pull_request) Failing after 7m51s
The walking-skeleton e2e timed out waiting for the Keycloak login form (`#username`). Root cause: in the compose network the portal is served over plain HTTP on a non-localhost origin (http://self-service), which is not a secure context, so Web Crypto (`crypto.subtle`) is undefined. angular-auth- oidc-client needs SubtleCrypto to build the PKCE code challenge, so `authorize()` threw ("Cannot read properties of undefined (reading 'digest')") and the login redirect never fired. Production serves the portal over HTTPS, where this works. Instead of terminating TLS in the throwaway e2e stack, tell Chromium to treat the origin as secure via --unsafely-treat-insecure-origin-as-secure. The flag is only honoured by the full Chromium build (new headless), not Playwright's default headless-shell, so pin channel: 'chromium'. Verified against a minimal in-network stack (keycloak + self-service): login redirect now reaches the Keycloak form, and the full login → token exchange → authenticated portal renders with no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2e00ad38ba |
ci(portal-self-service): run Vitest ahead of the production build to stop worker-start timeout (refs #68)
CI / lint (pull_request) Successful in 1m11s
CI / build (pull_request) Successful in 54s
CI / unit (pull_request) Successful in 1m3s
CI / frontend (pull_request) Successful in 2m0s
CI / mutation (pull_request) Successful in 4m5s
CI / verify-stack (pull_request) Failing after 10m20s
The frontend lane ran `nx run-many -t lint test build`, so the ~5min self-service production build shared nx's task pool with the Vitest test worker. @angular/build:unit-test's Vitest worker has hard-coded 60s/90s startup timeouts (not configurable); on a CPU-constrained CI runner the concurrent build starved the worker and it failed with "Timeout waiting for worker to respond" — flaky, since it passed on the prior commit. Split the target into a light lint+test phase and a separate build phase so tests get CPU and the worker starts well inside its window. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
be016f920c |
fix(portal-self-service): health-check nginx over IPv4 (127.0.0.1) (refs #68)
CI / lint (pull_request) Successful in 1m13s
CI / build (pull_request) Successful in 1m0s
CI / unit (pull_request) Successful in 1m6s
CI / frontend (pull_request) Failing after 7m11s
CI / mutation (pull_request) Successful in 3m59s
CI / verify-stack (pull_request) Failing after 7m47s
nginx listens on IPv4 only (listen 80), but 'localhost' inside the container resolves to ::1 first, so the wget healthcheck got connection-refused and self-service never went healthy — timing out the CI stack bring-up. Probe 127.0.0.1 instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d3f23a4da3 |
docs(portal-self-service): serving/e2e decisions + walking-skeleton demo note (refs #68)
CI / lint (pull_request) Successful in 1m5s
CI / unit (pull_request) Successful in 1m2s
CI / frontend (pull_request) Successful in 1m31s
CI / mutation (pull_request) Successful in 3m55s
CI / build (pull_request) Successful in 52s
CI / verify-stack (pull_request) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
490e7347b0 |
test(e2e): walking-skeleton Playwright happy path + verify-e2e lane (refs #68)
tests/e2e Playwright spec drives DigiD login (jan-burger/test123) → submit → confirmation against the compose-served portal. run-e2e-check.sh runs it inside the compose network (node container, browser installed at runtime) so the token issuer (keycloak:8080) matches the BFF authority (ADR-0010). Wired as verify-e2e (Makefile + verify chain + a verify-stack CI step). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4f311c9b5a |
ci(portal-self-service): serve the self-service app in compose (refs #68)
Add the self-service nginx service (build the app image, depends_on bff healthy + keycloak started, health-checked, host port 8140). Add it to WAIT_SVCS and the CI log dump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a55ba1160d |
feat(portal-self-service): runtime config + nginx serve/proxy image (refs #68)
The app loads /config.json at startup (main.ts) so the OIDC authority is set per environment from one build; appConfig becomes a factory and derives redirectUrl + secureApiOrigin from the app origin (same-origin as the BFF). A multi-stage Dockerfile builds the app and serves it via nginx, reverse-proxying /self-service + /openbaar to the bff (relative URLs → no CORS); nginx resolves the BFF at request time. The compose image bakes config.json with the keycloak:8080 authority so the browser's token issuer matches the BFF (ADR-0010). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4416d1f4ed |
Merge pull request 'feat(portal-self-service): NL DS + DigiD self-service submit form (closes #67)' (#71) from feat/67-self-service-form into main
Reviewed-on: #71 |
||
|
|
074101e836 |
fix(portal-self-service): run checkAuth() at startup to end the login redirect loop (refs #67)
CI / lint (pull_request) Successful in 1m6s
CI / build (pull_request) Successful in 53s
CI / unit (pull_request) Successful in 1m3s
CI / frontend (pull_request) Successful in 1m56s
CI / mutation (pull_request) Successful in 3m55s
CI / verify-stack (pull_request) Successful in 4m34s
Without an app-init auth check, the DigiD callback (?code=…) was never processed, so the guard kept seeing 'not authenticated' and re-triggered login — an infinite redirect loop. Add withAppInitializerAuthCheck() so checkAuth() runs before the router and guard, establishing the session on the callback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5089c2aea6 |
fix(portal-self-service): re-export the full Utrecht package from libs/ui (refs #67)
CI / frontend (pull_request) Successful in 1m30s
CI / lint (pull_request) Successful in 1m5s
CI / build (pull_request) Successful in 51s
CI / unit (pull_request) Successful in 59s
CI / mutation (pull_request) Successful in 3m54s
CI / verify-stack (pull_request) Has been cancelled
Importing UtrechtComponentsModule pulls every component it exports into the AOT compiler scope, so all must be resolvable through the ui barrel; a partial re-export failed a fresh build with NG3004 (masked locally by the Nx build cache, surfaced by nx serve / a --skip-nx-cache build). Re-export the whole package. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
29f3dcc6cf |
docs(portal-self-service): record NL DS + DigiD decisions and demo note (refs #67)
CI / lint (pull_request) Successful in 1m6s
CI / build (pull_request) Successful in 50s
CI / unit (pull_request) Successful in 1m0s
CI / verify-stack (pull_request) Successful in 5m46s
CI / frontend (pull_request) Successful in 1m27s
CI / mutation (pull_request) Successful in 3m56s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2c196245c2 |
feat(portal-self-service): implement the DigiD registration submit page (refs #67)
RegistrationPage shows the signed-in BSN and submits to the BFF via the generated api-client, confirming with the returned reference; built from NL Design System (Utrecht) components. Wire the guarded route + app providers (DigiD OIDC + token interceptor + HttpClient), the NL DS theme, and lang=nl. Component tests (Testing Library) + axe (WCAG 2.1 AA) pass; a guard test covers libs/auth. Replace the demo eslint depConstraints (scope:shop/shared) with a permissive default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
72c2bdfae7 |
test(portal-self-service): DigiD-guarded registration submit page (refs #67)
Scaffold libs/ui (NL Design System via Utrecht components) and libs/auth (DigiD OIDC over angular-auth-oidc-client: mockable AuthService, provider, token interceptor, authenticated guard). Failing component + axe tests for the RegistrationPage: it must show the signed-in BSN, submit to the BFF (mocked api-client) and confirm, with no WCAG 2.1 AA violations. The page is a stub, so the behaviour tests fail; green follows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |