Changelog & Roadmap¶
Changelog¶
v2.9.7 β Feature Release (April 3, 2026)¶
AI Assistant β CPRMV Legislation Provider¶
CprmvMcpProvider added to the MCP registry. Connects to the CPRMV HTTP MCP server at acc.cprmv.open-regels.nl/mcp using StreamableHTTPClientTransport β a remote HTTP endpoint, not a subprocess. Enabled via CPRMV_MCP_ENABLED=true; URL overridable via CPRMV_URL.
Three tools exposed: rules_rules__rule_id_path__get (retrieve rules from BWB, CVDR, or EU CELLAR by rule ID path), ref_ref__referencemethod___reference__get (resolve rules by Juriconnect reference), celex_cellar_by_celex__celexid___language___format__get (look up EU CELLAR publications by CELEX id).
config.cprmv added to Config: enabled (CPRMV_MCP_ENABLED, default false), url (CPRMV_URL).
AI Assistant β LDE Process Library Provider¶
LdeMcpProvider added. Spawns a custom lde-mcp stdio subprocess (src/mcp-servers/lde/index.ts in dev, dist/mcp-servers/lde/index.js in prod) that connects directly to the LDE lde_assets PostgreSQL database. Enabled via LDE_MCP_ENABLED=true and LDE_DATABASE_URL.
Six tools: bundle_list, bundle_get (deployed BPMN bundles with forms, documents, subprocesses, and DMN keys), form_list, form_get (full Camunda Form JSON schema), document_list, document_get (zones and bindings).
SSL handled by stripping sslmode from the connection URL and passing ssl: { rejectUnauthorized: true } to the pg Pool constructor directly β avoids the pg-connection-string sslmode=require deprecation warning.
config.lde added to Config: enabled (LDE_MCP_ENABLED, default false), databaseUrl (LDE_DATABASE_URL).
AI Assistant β LLM Provider Architecture¶
LlmProvider interface introduced in src/services/llm/LlmProvider.ts. Decouples the agentic loop from any specific SDK β mcpChat.service.ts has no direct dependency on Anthropic or OpenAI. Provider-agnostic types: AgentMessage, AgentToolUse, AgentToolResult, LlmStreamParams, LlmTurnResult.
LlmRegistry singleton maps model IDs to their owning provider. getAvailableModels() returns only models from providers where isAvailable() is true.
AnthropicLlmProvider registered with three models: claude-sonnet-4-20250514, claude-opus-4-20250514, claude-haiku-4-5-20251001. Enabled when ANTHROPIC_API_KEY is set.
OpenAILlmProvider registered with gpt-4o and gpt-4o-mini. Enabled when OPENAI_API_KEY is set.
GET /v1/mcp/models added β returns all available models with providerId and providerDisplayName. Used by the frontend model selector dropdown.
POST /v1/mcp/chat body extended with modelId: string β required field; returns 400 INVALID_REQUEST when absent.
Frontend: model selector dropdown rendered below the subtitle in the AI Assistant header. Hidden when only one model is available. First available model pre-selected on mount.
Caseworker Dashboard β Procesbibliotheek¶
New procesbibliotheek section added to the Home tab for all tenants whose leftPanelSections.home includes the entry (currently Utrecht, Amsterdam, Rotterdam, Den Haag, Flevoland). Publicly accessible (isPublic: true).
Fetches deployed BPMN bundles from the LDE public API (VITE_LDE_API_URL/bundles/public). A dedicated ldeApi Axios instance is used β no Keycloak Authorization header is sent. Cards show process name, bpmnProcessId, status badge (WIP/Actief/Concept), role badge (Standalone/Subprocess), and deployment date; expand to reveal forms, documents, DMN keys, and deployment ID.
ProcessBundle, BundleDeployedForm, BundleDeployedDocument types exported from api.ts. VITE_LDE_API_URL added to all env files and vite-env.d.ts.
v2.9.6 β Enhancement (April 2, 2026)¶
IOU β Gebruiksscenario indienen β UX improvements¶
Sub-step number badges in step 6 (Concrete Example) changed from filled blue circles (bg-blue-600 rounded-full) to slate rounded squares (bg-slate-500 rounded-md), eliminating the visual collision with the section header badges which share the same shape and colour. The size was reduced from w-6 h-6 to w-5 h-5 to keep them visually subordinate to the section headers, and font-mono applied so the counter numerals read as distinct from section numbers.
Step 6 now has a remove button per row β only rendered when more than one step is present to prevent accidental full deletion. The button turns red on hover to signal destructive intent.
Step 9 (Existing Materials) gains an optional file attachment zone below the existing material checkboxes β drag-and-drop or file picker, any file type, up to 5 files at 10 MB each.
Backend β new endpoint¶
POST /v1/public/upload-file added to public.routes.ts. Accepts a single file of any type via multipart/form-data (field name file), uploads it to the GitLab project uploads API using GITLAB_TOKEN, and returns the GitLab-generated markdown reference ({ success: true, data: { markdown } }). Uses a dedicated uploadAny multer instance without the image-only fileFilter used by the /feedback route.
The /use-case submission remains plain JSON (Content-Type: application/json). Attachments are pre-uploaded one-by-one via POST /v1/public/upload-file before the issue is created; the returned markdown references are appended as a ## Bijlagen Β· Attachments section in the issue body. This avoids a multer v2 req.body field-parsing failure that occurred when text fields were submitted alongside files in multipart/form-data β text fields arrived as undefined regardless of file presence.
Backend β development noise fix¶
ExternalTaskWorker.asyncResponseTimeout reduced to 5 000 ms when NODE_ENV !== 'production' (was 20 000 ms). The long-poll window exceeded the TCP keep-alive timeout on the network path between the local dev machine and the remote Operaton VM, causing repeated ECONNRESET poll errors in the development log. The worker still runs locally; only the poll window is shortened.
v2.9.5 β Feature Release (April 1, 2026)¶
Caseworker Dashboard β IOU tab (Flevoland)¶
New IOU top-nav tab added, tenant-scoped to the flevoland tenant via tenants.json β leftPanelSections.iou. The tab is visible without authentication; submission sections require login. Four sections:
| Section | Auth required | Description |
|---|---|---|
| Gebruiksscenario indienen | Yes | 10-section submission form (title, submitter, description, current situation, desired outcome, concrete example, legislation, affected parties, existing materials, priority). POSTs to POST /v1/public/use-case; organisation pre-filled as "Provincie Flevoland". |
| Feedback geven | Yes | Feedback form with submitter info, description, and screenshot upload β paste (Ctrl+V), drag-and-drop, or file picker; up to 5 images at 10 MB each. POSTs to POST /v1/public/feedback. |
| Actieve zaken | No | Read-only list of open GitLab issues via GET /v1/public/use-cases?state=opened. Expandable cards rendered with react-markdown + remark-gfm; parsed sections: Indiener table, Beschrijving, and Gewenst resultaat. |
| Archief | No | Same component as Actieve zaken with state=closed. |
The IOU badge count on the top-nav IOU tab is populated by IouZakenSection via an onCountChange callback β identical pattern to the task count badge on the Projecten tab.
IouZakenSection is shared by both list views; the WORK_ITEM_FIELDS constant controls which markdown sections are extracted and displayed per card. Main content area overflow corrected from flex-col to block so all long-form sections scroll correctly.
To enable the IOU tab for another tenant, add an iou key with the four section entries to that tenant's leftPanelSections in tenants.json. No code changes are required.
Backend β IOU public endpoints¶
GET /v1/public/use-cases added to public.routes.ts. Lists GitLab issues for GITLAB_PROJECT_PATH; supports ?state=opened (default) or ?state=closed; returns up to 100 items sorted by created_at descending. Returns iid, title, state, created_at, updated_at, web_url, labels, assignees, and description per item. No authentication required.
POST /v1/public/feedback added to public.routes.ts. Accepts multipart/form-data with fields name, org, role, contact, description, and up to 5 image files under the field name screenshots. Each image is first uploaded to the GitLab project uploads API; the returned markdown references are embedded in the issue body. Uses multer in-memory storage with a per-file 10 MB limit and an image-only fileFilter. No authentication required.
Both endpoints require GITLAB_TOKEN, GITLAB_BASE_URL, and GITLAB_PROJECT_PATH to be set. Missing configuration returns 503 GITLAB_NOT_CONFIGURED.
See IOU GitLab Integration for full setup instructions, environment variable reference, and the curl verification steps.
v2.9.4 β Feature Release (March 30, 2026)¶
AI Assistant β Multi-Source MCP Registry¶
McpClientService singleton replaced by McpRegistry β a provider registry that manages multiple independent MCP sources. Each provider connects, exposes a curated set of tools, and contributes a section to the composite system prompt independently. A provider failure does not block other providers.
McpProvider interface introduced: id, displayName, description, connect(), disconnect(), getToolDefinitions(), callTool(), isConnected(), systemPromptContribution().
OperatonMcpProvider replaces McpClientService β identical stdio subprocess behaviour, 15-tool ALLOWED_TOOLS curation gate preserved.
TriplyDbMcpProvider added β spawns the bundled triplydb-mcp stdio server; connects to the RONL SPARQL endpoint (stevengort/RONL). Exposes 11 tools: dmn_list, dmn_get, dmn_chain_links, dmn_enhanced_chain_links, dmn_semantic_equivalences, organization_list, service_list, rule_list, concept_list, service_rules_metadata, sparql_query. Enabled via TRIPLYDB_MCP_ENABLED=true.
McpRegistry.getToolDefinitions(providerIds?) and callTool() accept an optional provider ID filter. buildSystemPrompt(providerIds?) assembles a composite prompt from only the selected connected providers. getProviderMeta() returns metadata and connection status for all registered providers.
POST /v1/mcp/chat extended with sources: string[] β provider IDs selected by the user for the session. GET /v1/mcp/sources added β returns provider metadata and connection status.
Frontend: source selector toggle buttons rendered below the message history. All connected sources pre-selected by default; offline providers shown greyed-out. Send button and textarea disabled when no sources are selected. Header subtitle shows active source display names dynamically.
Markdown rendering added to assistant bubbles via react-markdown + @tailwindcss/typography prose classes. In-progress streaming bubble also renders Markdown incrementally.
v2.9.3 β Feature Release (March 26, 2026)¶
Caseworker Dashboard β Berichten & Regelcatalogus¶
- Berichten endpoint switched from hardcoded seed data to the Provincie Flevoland RSS feed (
flevoland.nl/Content/Pages/Loket?rss=news) β same axios/regex pattern as the Nieuws service, 10-minute cache TTL. - HTML entities decoded server-side (
nbsp,amp,euro,lt,gt,quot); action link populated from RSS<link>element as "Lees meer". getBerichtById()now reads from the live cache instead of the removedSEEDconstant;/berichtenand/berichten/:idroutes made async.BerichtenSectionfooter row now rendersitem.actionas a "Lees meer β" anchor, matching theNieuwsSectionpattern.- Berichten section moved above Nieuws in
leftPanelSections.homefor all tenants intenants.json. - Regelcatalogus default active tab changed from
dienstentoorganisaties.
Caseworker Dashboard β Producten & Diensten Catalogus¶
- New "Producten & Diensten" section added to the Flevoland tenant home panel β publicly accessible without login.
- Backend service fetches the Provincie Flevoland SC4.0 product feed (
flevoland.nl/loket/loketoverview?sc40=true) β XML parsed server-side with no additional dependency, 30-minute cache TTL. - New
GET /v1/public/producten-dienstenendpoint; returnsid,title,description,url,audience,onlineAanvragen, andmodifiedper item. ProductenDienstenCataloguscomponent: expandable 2-column card grid styled afterRegelCatalogus, with free-text search and audience filter (Alle / Ondernemer / Particulier).- Cards show audience badges and an "Online aanvragen" badge where applicable; expanded card links directly to the product page on flevoland.nl.
- Stats row shows total visible product count and number of online-aanvraagbare items.
- Main content area overflow corrected from
overflow-hiddentooverflow-y-autoβ all sections with long content lists are now fully scrollable.
AI Assistant β SSE Streaming¶
POST /v1/mcp/chatreplaced with SSE streaming βContent-Type: text/event-stream, headers flushed immediately,X-Accel-Buffering: noset for Caddy; three event types:status(tool call starting),delta(text token),done(loop complete).client.messages.stream()used in place ofmessages.create(); text deltas emitted immediately on all rounds so the user sees tokens arrive in real time.- Tool result payloads capped at 12,000 characters before being added to the messages array β prevents prompt-too-long errors on multi-round queries that return large Operaton JSON responses.
- Timeout raised to 240s for the SSE endpoint.
POST /v1/mcp/chatexcluded from audit log middleware alongsideGET /v1/admin/audit.AbortControllerthreaded through the streaming loop and tool execution: fires on client disconnect and on timeout.businessApi.mcp.chatStream()async generator inapi.tsreplaces the axios POST β refreshes Keycloak token first, then consumes the SSEReadableStreamline-by-line and yields typedMcpChatStreamEventobjects.McpChatSection: in-progress assistant bubble updates token-by-token ondeltaevents with a blinking cursor; status line above the typing dots shows the active tool name (e.g.Calling deployment_listβ¦) between rounds; Clear chat aborts any in-flight stream;AbortControllercancelled on unmount.
v2.9.2 β Refactor (March 23, 2026)¶
Regelcatalogus β tab order¶
Tab order changed to Organisaties β Diensten β Regels β Concepten. The TABS array in RegelCatalogus.tsx was reordered; no data or API changes.
Caseworker Dashboard β component extraction¶
CaseworkerDashboard.tsx reduced from ~2 500 lines to a pure shell responsible for auth state, tenant config, navigation state, and layout only β no domain logic remains in the page file. All sections extracted to src/components/CaseworkerDashboard/:
NieuwsSection,BerichtenSectionβ own their fetch lifecycle;PRIORITY_STYLESandTYPE_LABELSmoved intoBerichtenSectionArchiefSectionβ owns task history fetch, grouping logic, variable cache, and expand stateOnboardingArchiefSectionβ role-gated tohr-medewerker; owns completed onboarding list fetch andDecisionViewerexpand stateRipFase1WipSection,RipFase1GereedSectionβ role-gated toinfra-projectteam; each owns its own project list fetch and viewer expand stateGereedschapSectionβ owns all three status API calls (eDOCS, Operaton, external);PLATFORM_TOOLSconstant moved out of the page fileTakenSectionβ owns full task queue lifecycle including list fetch, select, claim,TaskFormViewerintegration, andonCountChangecallback for the top nav badgeHrOnboardingSection,RipFase1Sectionβ each owns its started/error state, eliminating the last uses of sharedactionMessagestateAuditSectionβ handles bothaudit-overzichtandaudit-detailstabs viaactiveTabprop, owns paginated fetch and load-more stateProfielSectionβ consumesuseProfielDatahook; ownsemployeeIdInputfor manual ID lookup fallbackRollenSectionβ consumesuseProfielDataindependently; derives onboarding roles and access level displayuseProfielDatahook introduced insrc/hooks/useProfielData.tsβ shared byProfielSectionandRollenSectionformatDateextracted tosrc/utils/formatDate.tsand shared across components
v2.9.1 β Feature Release (March 21, 2026)¶
Archive β Completed tasks¶
Archief section added to the Projecten tab. Completed tasks are fetched from the Operaton historic task API (GET /history/task?finished=true) via the new GET /v1/task/history backend endpoint. The endpoint is tenant-scoped via the municipality process variable and registered before /:id to prevent route shadowing.
OperatonService.getCompletedTasks(tenantId) fetches up to 200 completed tasks sorted by endTime descending.
In the frontend, tasks are grouped by processDefinitionKey β identical to the active task queue: mono uppercase group headers, groups sorted by most recent endTime. Each task card shows name, completion date, and assignee. Expanding a card loads historic process variables via the existing historicVariables endpoint; variables are cached per processInstanceId.
businessApi.task.history() added to api.ts with HistoricTask type from @ronl/shared.
v2.9.0 β Feature Release (March 20, 2026)¶
Caseworker Dashboard β Gereedschap¶
New Gereedschap top-nav page added as a platform-scoped tab β not tenant-configured, visible to all authenticated caseworkers regardless of organisation.
Eight tool cards: CPSV Editor, CPRMV API, TriplyDB, Linked Data Explorer, Operaton Cockpit, eDOCS, SAP, KMS. Each active tool opens in a new browser tab; placeholder tools (eDOCS, SAP, KMS) show an orange Binnenkort badge with no open button. Operaton Cockpit and SAP are only visible to users with the admin role.
Live status widgets:
| Tool | Source |
|---|---|
| Operaton Cockpit | GET /v1/health β existing health endpoint |
| eDOCS | GET /v1/edocs/status β stub/live/offline |
| CPRMV API, TriplyDB, LDE | GET /v1/health/external β server-side HEAD requests to avoid CORS |
GET /v1/health/external added to health.routes.ts. It performs parallel HEAD requests (5-second timeout) to acc.cprmv.open-regels.nl, api.open-regels.triply.cc, and acc.linkeddata.open-regels.nl, returning { status: "up"|"down", latency: number } per service.
Adding a new tool requires a single entry in the PLATFORM_TOOLS constant in GereedschapSection.tsx. No other code changes are required.
businessApi.externalStatus() added to api.ts. businessApi.health() error handling hardened to extract dependency data from axios 503 responses.
v2.8.2 β March 19, 2026¶
Audit log β database persistence fixes¶
persistAuditLog() in audit.service.ts refactored to pass an explicit named-parameter object to pg-promise instead of spreading AuditLogEntry. The spread caused pg-promise to throw Property 'resourceType' doesn't exist for any field not referenced in the SQL template (specifically azp added in v2.8.1), silently suppressing all audit log writes to the database on ACC.
ipAddress port stripping now applied in the explicit object β Azure App Service appends the port to req.ip, which is invalid for PostgreSQL inet type. This error was masked by the spread error and is now also fixed.
v2.8.1 β March 19, 2026¶
Audit log β M2M tenant fallback¶
persistAuditLog() now falls back to the azp claim when tenantId is absent, preventing a NOT NULL violation on tenant_id for service account tokens. The fallback is applied only at the point of DB persistence β req.user.tenantId is unchanged.
jwt.middleware.ts reverted: tenantId is set exclusively from the municipality claim. The earlier azp fallback on req.user caused tenantMiddleware to pass M2M tokens through to tenant-scoped routes, returning empty data instead of MISSING_TENANT.
azp?: string added to AuditLogEntry in audit.types.ts and to AuthContext in auth.types.ts. azp populated on req.auth in jwt.middleware.ts and passed through createAuditLog() β eliminates type casts in audit.middleware.ts.
v2.8.0 β March 19, 2026¶
M2M API β Operaton access¶
New /v1/m2m/* route group in m2m.routes.ts applies jwtMiddleware only β no tenantMiddleware. M2M clients are system actors not scoped to a single organisation, so tenant isolation is intentionally absent.
The full Operaton surface is exposed: process (list, start, status, variables, historic-variables, history, decision-document, start-form, variable-hints, delete), task (list, get, variables, form-schema, claim, complete), and decision (evaluate, get).
A M2M_ALLOWED_OPERATIONS constant at the top of m2m.routes.ts acts as a curation gate β comment out any entry to disable that operation with no other code changes required.
Dedicated Operaton instance supported via OPERATON_M2M_BASE_URL, OPERATON_M2M_USERNAME, OPERATON_M2M_PASSWORD β falls back to the shared instance when unset. On ACC, the M2M routes point at operaton-doc.open-regels.nl.
See Operaton MCP Client for the full setup, curl verification steps, and curation instructions.
OperatonService β new public methods and constructor¶
getUserTasks() parameters made optional β tenantId omitted returns an unfiltered task list; existing callers with tenantId are unaffected.
getTaskVariables(taskId) added: resolves processInstanceId via getTask(), returns flattened process variables.
listProcessInstances(params?), queryProcessHistory(body), and getDecisionDefinition(key) added as thin pass-throughs to Operaton with no tenant filter, intended for M2M callers.
OperatonService constructor updated to accept optional baseUrl, username, and password parameters β the existing singleton instantiation is unchanged.
Keycloak β operaton-mcp-client¶
New confidential client operaton-mcp-client registered in the ronl realm: service accounts enabled, Client Credentials grant only, audience mapper targeting ronl-business-api. No municipality or organisation_type claims β M2M client has no tenant context by design.
Audit log β M2M tenant fallback¶
extractUser() in jwt.middleware.ts falls back to the azp claim when municipality is absent, preventing a NOT NULL violation on tenant_id for service account tokens. M2M audit entries record tenant_id as the Keycloak client ID (e.g. operaton-mcp-client).
v2.7.0 β March 14, 2026¶
eDOCS Service β Live Mode¶
EdocsService ported to packages/backend/src/services/edocs.service.ts: session token caching via POST /connect, automatic re-authentication on 401/403, ensureWorkspace, uploadDocument, getWorkspaceDocuments, and healthCheck. When EDOCS_STUB_MODE=true (default) all methods return realistic fake responses β the stub is fully transparent to callers.
ExternalTaskWorker ported to packages/backend/src/services/externalTaskWorker.service.ts: long-polling Operaton's external task API on topics rip-edocs-workspace and rip-edocs-document. The worker starts inside the app.listen() callback and stops cleanly on SIGTERM/SIGINT.
edocs.routes.ts rewritten to delegate to EdocsService β all four endpoints (/status, /workspaces/ensure, /documents, /workspaces/:id/documents) are now backed by the service rather than hardcoded stub responses.
config.ts extended with an edocs block reading EDOCS_BASE_URL, EDOCS_LIBRARY, EDOCS_USER_ID, EDOCS_PASSWORD, and EDOCS_STUB_MODE. utils/errors.ts added with the getErrorMessage() helper.
Copilot Studio β eDOCS OAuth Connection¶
Keycloak client copilot-studio-edocs registered in ronl-realm: confidential, service accounts enabled, Client Credentials grant only, audience mapper targeting ronl-business-api. The OAuth 2.0 connection was verified end-to-end on ACC.
See Copilot Studio β eDOCS OAuth Integration for the full setup, curl verification steps and Live Mode switch.
v2.6.0 β Feature Release (March 13, 2026)¶
RIP Phase 1 β Process Bundle (Flevoland) ποΈ
RipPhase1ProcessBPMN deployed: 17-step process covering intake β eDOCS workspace β intake meeting β intake report β approval loop β PSU β PSU report β risk file β PDP β approval loop β end.RipProjectTypeAssignmentDMN mapsdepartment+projectTypetocandidateGroups(infra-projectteam) andassignedRoles(infra-medewerker). Hit policy FIRST; structured for per-role granularity in future iterations.- Seven task forms:
rip-intake,rip-intake-meeting,rip-intake-report,rip-psu-organize,rip-psu-execution,rip-risk-file,rip-approval(reusable at both approval gateways). - Three document templates bundled in deployment:
rip-intake-report.document(Column 2),rip-psu-report.document(Column 3),rip-pdp.document(Column 4). - eDOCS integration via Operaton external task pattern β LDE backend worker polls topics
rip-edocs-workspace(writesedocsWorkspaceId) andrip-edocs-document(writesedocsIntakeReportId,edocsPsuReportId,edocsPdpId). Stub mode (EDOCS_STUB_MODE=true) active by default. EmployeeRoleAssignmentDMN updated: allinfrastructuurdepartment rules prependinfra-projectteamtocandidateGroupsso onboarded infrastructure employees can claim RIP tasks without a separate configuration step.
RIP Phase 1 β Caseworker Dashboard ποΈ
- Projecten β RIP Fase 1 starten: role-gated to
infra-projectteam; startsRipPhase1Processwith a single button; success state directs to Taken. - Projecten β RIP Fase 1 WIP: lists all active
RipPhase1Processinstances for the municipality, enriched withprojectNumber,projectName,edocsWorkspaceId, and start date. Expands to three collapsible document sections (Intakeverslag, PSU-verslag, Voorlopige Ontwerpuitgangspunten); documents not yet produced show "Nog niet beschikbaar". - Projecten β RIP Fase 1 gereed: identical layout to WIP; shows completed instances with completion date via
GET /v1/rip/phase1/completed. - Document rendering reuses the TipTap/ProseMirror zone renderer from
DecisionViewerwith zone key normalisation (signoff/signOff,contactInfo/contactInformation).
Backend β RIP Phase 1 Endpoints βοΈ
GET /v1/rip/phase1/activeβ lists activeRipPhase1Processinstances for the caseworker's municipality.GET /v1/rip/phase1/:instanceId/documentsβ returns all three document templates from the deployment bundle plus current process variables in a single response; absent documents returnnull.GET /v1/rip/phase1/completedβ lists completedRipPhase1Processinstances enriched withendTime.- All three endpoints apply municipality-based tenant isolation consistent with all other process routes.
- eDOCS endpoints:
GET /v1/edocs/status,POST /v1/edocs/workspaces/ensure,POST /v1/edocs/documents,GET /v1/edocs/workspaces/:id/documents.
Keycloak β Flevoland RIP Roles π
infra-projectteamandinfra-medewerkerrealm roles added toronl-realm.json.test-infra-flevolandtest user added with rolescaseworker,infra-projectteam,infra-medewerkerand attributesmunicipality=flevoland,employeeId=EMP-FLV-001.
Caseworker Dashboard β UX β¨
- Procesgegevens panel restyled to match RIP WIP document sections β bordered card with consistent β²/βΌ toggle.
roleResultintermediate DMN variable excluded from Procesgegevens display.- RIP WIP zone key normalisation fixes crash when expanding Intakeverslag.
Session Expiry Warning β±οΈ
SessionExpiryWarningcomponent mounted in the caseworker dashboard β polls token expiry every 15 seconds and shows a modal when fewer than 2 minutes remain.- Modal offers Sessie verlengen (forces
updateToken) and Uitloggen; unsaved form data is preserved when extending. - Axios request interceptor upgraded to proactively call
updateToken(30)before every API request; forces re-login if the SSO session is gone.
v2.5.1 β Enhancement (March 12, 2026)¶
Caseworker Dashboard β Changelog Panel π
- Changelog panel button added to the caseworker dashboard header, mirroring the button already present on the login page.
- Button positioned to the right of the authenticated user block for consistent right-side placement.
- Accessible without login β visible to unauthenticated visitors alongside the public sections.
Nieuws β Government.nl RSS Feed π°
- Nieuws endpoint switched from the Rijksoverheid JSON API to the Government.nl RSS feed (
feeds.rijksoverheid.nl/nieuws.rss). - RSS parsed server-side with no additional dependency β
axiosresponseType: textwith regex-based item extraction. - Source attribution updated to Government.nl; CDATA and plain-text description fields both handled correctly.
- 10-minute cache TTL retained; stale cache returned on feed unavailability to prevent blank UI.
v2.5.0 β Feature Release (March 12, 2026)¶
Caseworker Dashboard β Regelcatalogus π
- New public section "Regelcatalogus" added to the Home tab β accessible without caseworker login.
- Diensten tab: Public services from the RONL knowledge graph displayed as expandable cards with full description and URI link; clicking "Toon concepten" navigates to the Concepten tab pre-filtered by that service.
- Organisaties tab: Implementing organisations with logo (TriplyDB assets API), homepage, and linked services.
- Concepten tab: NL-SBB concepts searchable by label, filterable by service; each concept has a direct link to the
skos:exactMatchURI. - Regels tab: Implementation rules grouped by service; searchable by rule name and description; groups expand automatically when searching; description expandable per rule.
Backend β Regelcatalogus Endpoint βοΈ
GET /v1/public/regelcatalogusβ no authentication required; returns services, organisations, concepts, and rules in a single response.- Five parallel SPARQL queries against the RONL TriplyDB endpoint:
PublicService,PublicOrganisation, competent authority links, NL-SBB concept traversal, andcpsv:Ruleimplementations. - Organisation logos resolved via TriplyDB assets API to versioned CDN URLs.
- 5-minute in-memory cache per data slice; stale cache returned on TriplyDB failure to prevent blank UI.
RONL_SPARQL_ENDPOINTenvironment variable added for overriding the default endpoint per deployment.
v2.4.1 β Feature Release (March 11, 2026)¶
Multi-Tenant Architecture β Organisation Types ποΈ
- Platform extended beyond municipalities: provinces and national government agencies now supported as first-class tenant categories.
- New
OrganisationTypeunion type:municipality | province | nationalβ shared across frontend, backend, and Keycloak (@ronl/shared). organisationTypeJWT claim added to all tokens via Keycloak protocol mapper (organisation_typeuser attribute).organisationTypepropagated throughAuthenticatedUser,JWTPayload, and BPMN process variables.TenantConfiggainsorganisationType(required) andorganisationCode(optional, for CBS PV codes, OIN, etc.);municipalityCodemade optional.tenants.jsonextended with Provincie Flevoland (province,PV24) and UWV (national) as reference tenants.- Backend error messages generalised: "municipality mismatch" β "organisation mismatch".
- PostgreSQL
tenantstable gainsorganisation_typeandorganisation_codecolumns. - Keycloak realm:
organisation_typeattribute and protocol mapper added; test users forflevolandanduwvadded.
v2.4.0 β Feature Release (March 11, 2026)¶
HR Onboarding Process π€
HrOnboardingProcessBPMN deployed: collect employee data β DMN role assignment β HR review β notify employee.EmployeeRoleAssignmentDMN mapsdepartment+jobFunctiontoassignedRoles,candidateGroups, andaccessLevel.- All user tasks use
candidateGroups="hr-medewerker"β claim-first workflow identical to Kapvergunning. - Process started with empty variables; first task (Collect employee data) appears in the task queue immediately.
hr-medewerkerrealm role added;test-hr-denhaagandtest-onboarded-denhaagtest users added for Den Haag.employeeIdprotocol mapper added toronl-business-api-dedicatedclient scope β injectsemployee_iduser attribute asemployeeIdJWT claim.
IT Handover Document π
hr-it-handover.documentauthored and bundled inHrOnboardingProcessdeployment.- Document linked via
ronl:documentRefonTask_NotifyEmployeeinHrOnboardingProcess.bpmn. - Template includes medewerkergegevens, toegangsspecificaties, and step-by-step Keycloak account creation instructions for IT.
- Bindings cover
employeeId,firstName,lastName,municipality,department,jobFunction,assignedRoles,candidateGroups,accessLevel,startDate.
Caseworker Dashboard β HR Sections ποΈ
- Persoonlijke info β Profiel: JWT identity card + onboarding data auto-fetched via
employeeIdclaim; manual input fallback when claim absent. - Persoonlijke info β Rollen & rechten: Assigned roles from completed onboarding process with access level description card.
- Persoonlijke info β Medewerker onboarden: Role-gated to
hr-medewerker; startsHrOnboardingProcesswith a single button; success state directs to task queue. - Persoonlijke info β Afgeronde onboardingen: Role-gated to
hr-medewerker; lists all completedHrOnboardingProcessinstances for the municipality with name, employee ID, and completion date; expand to render IT handover document viaDecisionViewer. GET /v1/hr/onboarding/profileβ returns flattened historic variables for a completed onboarding byemployeeId+ municipality.GET /v1/hr/onboarding/completedβ returns list of all completed onboarding instances enriched withemployeeId,firstName,lastName.
Caseworker Dashboard β UX Fixes β¨
- Header user block shows
preferred_username, LoA badge, and all role badges dynamically β supports multiple roles. - Unauthenticated navigation to any top-nav page now defaults to the first section in the left panel, showing the login prompt immediately without a second click.
- Afgeronde onboardingen access restricted to
hr-medewerkerrole β regular caseworkers see access-denied message.
v2.3.0 β Feature Release (March 9, 2026)¶
Citizen Dashboard β Document Template Viewer π
DecisionViewernow fetchesGET /v1/process/:id/decision-documentin parallel with historic variables. When aDocumentTemplateis bundled in the Operaton deployment, it is rendered as styled HTML β TipTap/ProseMirror JSON blocks converted to React elements,{{variableKey}}placeholders substituted from historic process variables. The letter layout (letterhead + contact information side-by-side, body, closing, sign-off, optional annex) mirrors the Document Composer canvas.- Falls back to the v2.2.0 form-js readonly schema for process instances deployed before document templates were introduced.
Backend β Decision Document Endpoint βοΈ
GET /v1/process/:id/decision-documentβ reads theronl:documentRefattribute from the BPMNUserTaskelement via the process definition XML, fetches the named.documentresource from the Operaton deployment bundle, and returns{ success: true, template: DocumentTemplate }.- Tenant isolation applied via
municipalityvariable β same pattern ashistoric-variables. - Returns 404
DOCUMENT_NOT_FOUNDwhen noronl:documentRefis present or the.documentresource is absent from the deployment bundle. - Route ordering in
process.routes.tscorrected: literal/historyroute and instance-ID sub-routes registered before definition-key sub-routes.
LDE β BPMN Document Linking π
BpmnCanvasproperties panel writesronl:documentRef="<templateId>"into the BPMN XML when a document template is linked to aUserTask.- The
ronlnamespace (http://ronl.nl/schema/1.0) is declared on the BPMNdefinitionselement. - The linked document template is bundled as a
.documentJSON file in the one-click deployment alongside BPMN and.formfiles.
v2.2.0 β Feature Release (March 5, 2026)¶
Citizen Dashboard β Dynamic Start Form π³
- Kapvergunning form replaced by
@bpmn-io/form-jsviewer β schema fetched live from the deployed process viaGET /v1/process/:key/start-form. - Form renders with
applicantIdandproductTypepre-populated as hidden initial data. - On submit, form variables are passed directly to
POST /v1/process/:key/startβ no hardcoded field mapping. - Falls back gracefully when no form is deployed (404/415).
Caseworker Dashboard β Dynamic Task Forms ποΈ
CaseReviewFormandNotifyApplicantFormreplaced by a singleTaskFormViewercomponent.- Form schema fetched per task via
GET /v1/task/:id/form-schemawith tenant isolation. - Process variables pre-populated into the form at import time β caseworker sees current DMN decisions immediately.
- FEEL conditional visibility on the
tree-felling-reviewform hides override fields unless caseworker selects Wijzigen. - Falls back to a generic "Taak voltooien" button when no form is deployed (
status === 'no-form').
Citizen Dashboard β Decision Viewer π
- Completed applications in Mijn aanvragen show a Bekijk beslissing toggle.
DecisionViewerfetches final variable state viaGET /v1/process/:id/historic-variables.- Readonly form renders
status,permitDecision,finalMessage,replacementInfo, anddossierReferenceβ caseworker-only fields excluded. - Historic variables available immediately after process completion β no polling required.
Backend β Form Schema Endpoints βοΈ
GET /v1/process/:key/start-formβ fetches deployed start form schema; returns 415UNSUPPORTED_FORM_TYPEfor legacy HTMLformKeydeployments.GET /v1/task/:id/form-schemaβ fetches deployed task form schema with tenant isolation; treats Operaton 400 (noformRefset) as 404FORM_NOT_FOUND.POST /api/dmns/process/deployβ deploys BPMN + subprocess BPMNs + Camunda Forms in one multipart request.
v2.1.0 β Feature Release (March 3, 2026)¶
AWB Kapvergunning Process π³
- Full two-layer AWB process implementation.
AwbShellProcessmanages the procedural framework (Awb phases 1β6): identity recording, receipt acknowledgement withdossierReferenceand statutory 8-week deadline (Awb 4:13), admissibility check viaAwbCompletenessCheckDMN (Awb 2:3), and citizen notification confirmation. TreeFellingPermitSubProcesshandles the substantive decision: bothTreeFellingDecisionandReplacementTreeDecisionDMNs are always evaluated before the caseworker review task, giving the caseworker full context.Sub_ResolveDecisionapplies overrides whenreviewAction = "change".camunda:historyTimeToLiveset to 365 days (shell) and 180 days (subprocess).
Caseworker Task Queue β Claim-First Workflow ποΈ
- All user tasks (
Sub_CaseReview,Task_Phase6_Notify,Task_RequestMissingInfo) now usecamunda:candidateGroups="caseworker"instead ofcamunda:assignee. - Tasks appear as Openstaand in the task queue and require an explicit claim before the action form is displayed.
- Removed dead
Task_ExtractCompletenessscriptTask fromAwbShellProcess(had no incoming or outgoing flows, was never executed).
Backend β Tenant Variable Serialisation βοΈ
- Tenant middleware now stores plain scalar values.
- Process start routes wrap with
inferType()before forwarding to Operaton. - Resolves
Must provide 'null' or String value for value of SerializableValue type 'Json'500 error onAwbShellProcessstart.
Frontend β v2.0.1 β Feature Release (February 27, 2026)¶
Caseworker login π’
Added a dedicated caseworker login path to the MijnOmgeving landing page. A slate-coloured "Inloggen als Medewerker" button, visually separated from the three citizen IdP options by a "MEDEWERKERS" section divider, initiates the new flow. AuthCallback uses check-sso instead of login-required, so caseworkers with an active Keycloak SSO session bypass the login screen on subsequent visits. When a new session is required, keycloak.login({ loginHint: '__medewerker__' }) redirects to Keycloak, where the custom login.ftl theme detects the sentinel and renders an indigo "Inloggen als gemeentemedewerker" context banner with "Medewerker portaal" as the page title.
Frontend β v2.0.0 β Major Release (February 2026)¶
Frontend Redesign π¨
- New landing page with identity provider selection (DigiD / eHerkenning / eIDAS)
- Custom Keycloak theme matching MijnOmgeving design
- Blue gradient header with rounded modern inputs
- Multi-tenant theming with CSS custom properties for runtime theme switching
- Dutch language support throughout authentication flow
- Mobile-responsive design for all screen sizes
Authentication Flow π
- Identity Provider selection before Keycloak authentication
- DigiD, eHerkenning, and eIDAS support (infrastructure ready)
- Seamless redirect flow with
idpHintparameter - Session storage for IDP selection persistence
- Enhanced error handling and user feedback
Infrastructure ποΈ
- Azure Static Web Apps deployment with SPA fallback routing
- Custom Keycloak theme deployment to VM
- Theme volume mounting for ACC and PROD environments
- Version-controlled deployment configurations
- Manual deployment process for VM-hosted services
Frontend β v1.5.0 β Feature Release (February 2026)¶
Multi-Tenant Support ποΈ
- Four municipalities supported: Utrecht, Amsterdam, Rotterdam, Den Haag
- Municipality-specific theming with custom colours and logos
- Tenant configuration via JSON for runtime theme switching
- Municipality claim in JWT tokens for backend tenant isolation
- Test users for each municipality with proper attributes
Zorgtoeslag Calculator π°
- DMN-based zorgtoeslag (healthcare allowance) calculation
- Integration with Operaton BPMN/DMN engine
- Business rules evaluation via REST API
- Result display with matched rules and annotations
- Support for multiple requirement checks and income thresholds
Security & Compliance π
- JWT audience validation for API security
- Role-based access control (citizen, caseworker, admin)
- Assurance level (LoA) claims for DigiD compliance
- Audit logging with 7-year retention
- BIO (Baseline Information Security) compliance ready
Backend / Frontend β v1.0.0 β Initial Release (JanuaryβFebruary 2026)¶
Status: Production
Released: February 2026
Backend Core
- Secure Business API Layer for Dutch municipality government services
- OIDC Authorization Code Flow + PKCE via Keycloak 23
- Multi-tenant isolation for Utrecht, Amsterdam, Rotterdam, Den Haag
- JWT validation with JWKS caching (Redis)
- Zorgtoeslag calculation via Operaton BPMN/DMN
- Compliance-grade audit logging (PostgreSQL, 7-year retention)
- Rate limiting per IP and per tenant
- Helmet security headers (CSP, HSTS)
- Versioned REST API (
/v1/*) following Dutch API Design Rules - Deprecated
/api/*routes withDeprecationheaders
Frontend Core ποΈ
- Monorepo structure with frontend, backend, and shared packages
- React 18 + TypeScript frontend with Vite build
- Express + TypeScript backend with PostgreSQL
- Keycloak 23.0 for authentication and authorisation
- Operaton integration for BPMN/DMN execution
Deployment π
- Azure Static Web Apps for frontend (ACC + PROD)
- Azure App Service for backend API
- VM-hosted Keycloak with separate ACC/PROD instances
- Caddy reverse proxy for SSL termination
- GitHub Actions for automated deployments
- Multi-tenant frontend theming via CSS custom properties
- Dynamic
tenants.jsonconfiguration (no rebuild needed for theme changes)
Supported municipalities
Utrecht, Amsterdam, Rotterdam, Den Haag β each with isolated data, custom theme, role-based access, and dedicated audit logs.
Technology versions
| Component | Version |
|---|---|
| Node.js | 20 |
| React | 18 |
| TypeScript | 5.3 |
| Keycloak | 23 |
| Express | 4.18 |
| Vite | Latest |
| Caddy | 2 |
| PostgreSQL | 16 |
Roadmap¶
Completed¶
| Feature | Version |
|---|---|
| Monorepo core architecture | v1.0.0 |
| Multi-tenant municipality support | v1.5.0 |
| Zorgtoeslag DMN calculator | v1.5.0 |
| IDP selection landing page | v2.0.0 |
| Custom Keycloak MijnOmgeving theme | v2.0.0 |
| DigiD / eHerkenning / eIDAS infrastructure | v2.0.0 |
| Caseworker login with SSO session reuse | v2.0.1 |
| CI/CD Vite environment configuration | v2.0.2 |
| AWB Kapvergunning process (AwbShellProcess + subprocess) | v2.1.0 |
| Caseworker claim-first task queue | v2.1.0 |
| BPMN design criteria reference documentation | v2.1.0 |
| Dynamic Camunda Forms β citizen start form | v2.2.0 |
| Dynamic Camunda Forms β caseworker task forms | v2.2.0 |
| Decision Viewer β citizen-facing historic variables | v2.2.0 |
| Decision Document Viewer β DocumentTemplate rendering | v2.3.0 |
| Backend decision-document endpoint | v2.3.0 |
LDE BPMN document linking (ronl:documentRef) |
v2.3.0 |
| HR Onboarding Process (BPMN + DMN) | v2.4.0 |
| IT Handover Document template | v2.4.0 |
| Caseworker Dashboard β HR sections | v2.4.0 |
| Multi-tenant organisation types (province, national) | v2.4.1 |
OrganisationType claim in JWT |
v2.4.1 |
| Caseworker Dashboard β Regelcatalogus | v2.5.0 |
| Backend Regelcatalogus endpoint (SPARQL + cache) | v2.5.0 |
| Changelog Panel in caseworker dashboard header | v2.5.1 |
| Nieuws β Government.nl RSS feed | v2.5.1 |
| RIP Phase 1 process bundle (Flevoland) | v2.6.0 |
| eDOCS integration β external task worker + stub mode | v2.6.0 |
| RIP Fase 1 starten / WIP / gereed dashboard sections | v2.6.0 |
infra-projectteam and infra-medewerker realm roles |
v2.6.0 |
| Session expiry warning modal + proactive token refresh | v2.6.0 |
Audit log β database persistence (audit_logs table) |
v2.7.1 |
| Audit log tab in caseworker dashboard | v2.7.1 |
| Commercial organisation type + cross-tenant processing | v2.7.3 |
M2M API β /v1/m2m/* route group |
v2.8.0 |
operaton-mcp-client Keycloak client |
v2.8.0 |
| Audit log β database persistence fixes | v2.8.2 |
| Gereedschap platform tools hub | v2.9.0 |
| Archief β completed task history | v2.9.1 |
| CaseworkerDashboard.tsx component extraction | v2.9.2 |
| Berichten β live Provincie Flevoland RSS feed | v2.9.3 |
| Producten & Diensten Catalogus (Flevoland) | v2.9.3 |
| AI Assistant β SSE streaming + TriplyDB Knowledge Graph | v2.9.3 |
| AI Assistant β Multi-Source MCP Registry (McpRegistry) | v2.9.4 |
| TriplyDbMcpProvider + Knowledge Graph tools | v2.9.4 |
| Source selector UI + Markdown rendering in chat bubbles | v2.9.4 |
| IOU tab β GitLab integration (Flevoland) | v2.9.5 |
GET /v1/public/use-cases, POST /v1/public/feedback |
v2.9.5 |
| IOU form UX β step badges, add/remove, file attachments | v2.9.6 |
POST /v1/public/upload-file |
v2.9.6 |
| CPRMV Legislation Provider | v2.9.7 |
| LDE Process Library Provider | v2.9.7 |
| LLM Provider Architecture (LlmRegistry, OpenAI support) | v2.9.7 |
| Procesbibliotheek section | v2.9.7 |
Planned¶
Phase 2 β Identity Provider Activation (2026 Q2)
Live DigiD integration with BSN-based citizen authentication. eHerkenning activation for business users. eIDAS support for EU residents. Full SAML federation with Dutch government identity infrastructure.
Phase 3 β Extended Business Rules (2026 Q2βQ3)
Additional DMN-based benefit calculations beyond zorgtoeslag. Parameterised rule sets loaded from TriplyDB. Integration with CPSV Editor published service definitions. Case management workflow with caseworker assignment and review.
Phase 4 β BRP Integration (2026 Q3)
Real-time citizen data retrieval from BRP (Basisregistratie Personen). Pre-populated forms using authenticated citizen profile. Timeline navigation for historische persoonsgegevens.
Phase 5 β Audit & Compliance Dashboard (2026 Q4)
Real-time audit log viewer for municipality administrators. Compliance reporting against BIO baseline. DPIA (Data Protection Impact Assessment) evidence export. Role-based access management UI.