diff --git a/openspec/changes/add-server-side-kpi-aggregation/specs/kpi-aggregation/spec.md b/openspec/changes/add-server-side-kpi-aggregation/specs/kpi-aggregation/spec.md
deleted file mode 100644
index 0d9e4775..00000000
--- a/openspec/changes/add-server-side-kpi-aggregation/specs/kpi-aggregation/spec.md
+++ /dev/null
@@ -1,211 +0,0 @@
-# Delta: add-server-side-kpi-aggregation
-
-Capability: `kpi-aggregation` (new)
-Affects canonical spec: `openspec/specs/dashboard/spec.md`
-
-## ADDED Requirements
-
-### REQ-KPI-001: KPI Aggregation Endpoint [V1]
-
-The system MUST expose a `GET /index.php/apps/procest/api/dashboard/kpis` endpoint that returns pre-computed dashboard KPI values as a JSON object.
-
-**Feature tier**: V1
-
-#### Scenario KPI-001a: Endpoint returns stable JSON shape
-
-- GIVEN an authenticated user sends `GET /index.php/apps/procest/api/dashboard/kpis`
-- THEN the response MUST have status code 200
-- AND the response MUST have `Content-Type: application/json`
-- AND the body MUST contain all of the following fields with their types:
- - `openCount` (integer ≥ 0) — count of cases with no `endDate` (not yet closed)
- - `newToday` (integer ≥ 0) — count of cases whose `startDate` matches today's date
- - `overdueCount` (integer ≥ 0) — count of open cases where `deadline` < today
- - `completedCount` (integer ≥ 0) — count of cases whose `endDate` falls in the current calendar month
- - `taskCount` (integer ≥ 0) — count of tasks assigned to the current user with status `available` or `active`
- - `tasksDueToday` (integer ≥ 0) — count of those tasks whose `dueDate` matches today
- - `statusBreakdown` (array of `{status: string, count: integer}`) — open case counts grouped by status value
- - `avgProcessingDays` (number ≥ 0, or `null` if no completed cases this month) — average days between `startDate` and `endDate` for cases completed in the current calendar month, computed via `AVG(DATEDIFF(endDate, startDate))`
- - `computedAt` (ISO 8601 datetime string) — when the DB queries that produced this result were executed
- - `cacheHit` (boolean) — whether the response was served from cache
-
-#### Scenario KPI-001b: Endpoint requires authentication
-
-- GIVEN an unauthenticated request to `GET /index.php/apps/procest/api/dashboard/kpis`
-- THEN the response MUST have status code 401
-- AND no KPI data MUST be returned
-
-#### Scenario KPI-001c: Zero values when no data exists
-
-- GIVEN Procest is freshly installed with no cases or tasks
-- WHEN an authenticated user requests `GET /api/dashboard/kpis`
-- THEN all integer fields MUST be `0`
-- AND `statusBreakdown` MUST be `[]`
-- AND the response MUST have status code 200 (not an error)
-
-#### Scenario KPI-001d: Error resilience
-
-- GIVEN the database is temporarily unavailable
-- WHEN the endpoint is called
-- THEN the response MUST have status code 500
-- AND the response body MUST contain an `error` field with a descriptive message
-- AND no partial or misleading data MUST be returned
-
----
-
-### REQ-KPI-002: DB-Side Aggregation [V1]
-
-All KPI counts MUST be computed by the database engine using `COUNT(*)`, `GROUP BY`, and filter predicates — never by loading object arrays into PHP and counting them in memory.
-
-**Feature tier**: V1
-
-#### Scenario KPI-002a: Correct count at scale
-
-- GIVEN 5,000 cases exist in the Procest register
-- AND all 5,000 are open (no `endDate`)
-- WHEN `GET /api/dashboard/kpis` is called
-- THEN `openCount` MUST be `5000`
-- AND the response time MUST be under 500ms
-- AND no `_limit` cap MUST silently truncate the count
-
-#### Scenario KPI-002b: No PHP-side array iteration for counts
-
-- GIVEN a code audit of `KpiAggregationService`
-- THEN it MUST NOT load case or task objects into PHP arrays for the purpose of counting
-- AND it MUST use `IDBConnection::getQueryBuilder()` with `COUNT(*)` or `selectAlias($qb->func()->count(...), 'cnt')`
-
----
-
-### REQ-KPI-003: Per-User Cache [V1]
-
-The endpoint MUST cache results per authenticated user with a 60-second TTL via Nextcloud's `ICacheFactory`.
-
-**Feature tier**: V1
-
-#### Scenario KPI-003a: Cache hit within TTL
-
-- GIVEN user "jan" called `GET /api/dashboard/kpis` 30 seconds ago
-- WHEN user "jan" calls the endpoint again
-- THEN the response MUST be served from cache (no DB queries run)
-- AND `cacheHit` in the response MUST be `true`
-- AND the `computedAt` timestamp MUST match the original computation time (not "now")
-
-#### Scenario KPI-003b: Cache miss after TTL expiry
-
-- GIVEN user "jan" called the endpoint 61 seconds ago
-- WHEN user "jan" calls the endpoint again
-- THEN the system MUST run fresh DB queries
-- AND `cacheHit` MUST be `false`
-- AND `computedAt` MUST reflect the new query time
-
-#### Scenario KPI-003c: Cache isolation between users
-
-- GIVEN user "jan" has 10 open cases and user "maria" has 5 open cases
-- WHEN both call `GET /api/dashboard/kpis` simultaneously
-- THEN "jan" MUST receive `openCount: 10` and "maria" MUST receive `openCount: 5`
-- AND the cache for "jan" MUST NOT be served to "maria"
-
-#### Scenario KPI-003d: Cache fallback when APCu unavailable
-
-- GIVEN APCu is disabled on the server
-- WHEN `GET /api/dashboard/kpis` is called
-- THEN the endpoint MUST still return correct data
-- AND it MUST run DB queries on every request (no error, just uncached)
-
----
-
-### REQ-KPI-004: Cache Invalidation on Object Changes [V1]
-
-The KPI cache for a user MUST be invalidated (without eager recomputation) when that user performs an object create, update, or delete operation in the Procest register.
-
-**Feature tier**: V1
-
-#### Scenario KPI-004a: Cache invalidated after case creation
-
-- GIVEN user "jan"'s KPI cache is populated (`openCount: 10`)
-- WHEN "jan" creates a new case (triggering `ObjectCreatedEvent`)
-- AND "jan" calls `GET /api/dashboard/kpis`
-- THEN the response MUST reflect the newly created case (`openCount: 11`)
-- AND `cacheHit` MUST be `false` (fresh computation)
-
-#### Scenario KPI-004b: Cache version bump mechanism
-
-- GIVEN user "jan"'s current cache version is `3`
-- WHEN an `ObjectUpdatedEvent` fires for a change made by "jan"
-- THEN the listener MUST increment the version key to `4`
-- AND the old version `3` data MAY remain in the cache store until its TTL expires (it will never be served again because the lookup key has changed)
-
-#### Scenario KPI-004c: Other user's cache unaffected
-
-- GIVEN user "jan" creates a case
-- THEN user "maria"'s KPI cache version MUST NOT be bumped
-- AND maria's next `GET /api/dashboard/kpis` MAY still return cached data
-
----
-
-### REQ-KPI-005a: Average Processing Days [V1]
-
-The endpoint MUST compute `avgProcessingDays` as the SQL average of date differences between `endDate` and `startDate` for cases whose `endDate` falls in the current calendar month.
-
-**Feature tier**: V1
-
-#### Scenario KPI-005a-1: avgProcessingDays for current month
-
-- GIVEN 4 cases were completed this calendar month with processing durations 3, 5, 7, and 9 days
-- WHEN `GET /api/dashboard/kpis` is called
-- THEN `avgProcessingDays` MUST equal `6.0` (the SQL `AVG(DATEDIFF(endDate, startDate))` result)
-- AND it MUST be computed by the database (`AVG(DATEDIFF(endDate, startDate))`), not by PHP-side iteration
-
-#### Scenario KPI-005a-2: No completed cases this month
-
-- GIVEN no cases have been completed in the current calendar month
-- WHEN the endpoint is called
-- THEN `avgProcessingDays` MUST be `null` (not `0`, since no data is different from "0 days")
-
-#### Scenario KPI-005a-3: avgProcessingDays uses endDate IS NOT NULL filter
-
-- GIVEN open cases (with `endDate IS NULL`) exist alongside completed cases
-- THEN open cases MUST be excluded from the AVG computation
-- AND only cases with both `startDate IS NOT NULL` and `endDate IS NOT NULL` MUST be included
-
----
-
-### REQ-KPI-005: Permission-Scoped Task Counts [V1]
-
-Task KPI counts (`taskCount`, `tasksDueToday`) MUST be scoped to the requesting user's assigned tasks. Case counts are register-wide for v1 (matching current behaviour).
-
-**Feature tier**: V1
-
-#### Scenario KPI-005a: Task count scoped to assignee
-
-- GIVEN user "jan" has 3 tasks assigned and user "maria" has 7 tasks assigned
-- WHEN "jan" calls `GET /api/dashboard/kpis`
-- THEN `taskCount` MUST be `3` (not `10`)
-
-#### Scenario KPI-005b: Case counts are register-wide for v1
-
-- GIVEN user "jan" views the dashboard
-- THEN `openCount`, `overdueCount`, `completedCount`, and `newToday` reflect all cases in the Procest register, not just Jan's assigned cases
-- AND this matches the current client-side behaviour where `fetchCollection('case')` returns all accessible cases
-
-#### Scenario KPI-005c: statusBreakdown is register-wide
-
-- GIVEN the register has 20 open cases with 3 different statuses
-- WHEN any authenticated user calls `GET /api/dashboard/kpis`
-- THEN `statusBreakdown` MUST list all 3 statuses with their respective counts
-
----
-
-## Performance Requirements (ADDED)
-
-- `GET /api/dashboard/kpis` MUST respond in < 100ms on a cache hit.
-- `GET /api/dashboard/kpis` MUST respond in < 500ms on a cache miss for up to 10,000 objects.
-- Response time MAY exceed 500ms for > 10,000 objects; adding a generated column index on the status JSON field is the recommended mitigation if this threshold is reached in production.
-- The endpoint MUST NOT perform N+1 queries — all counts MUST be computed in a fixed number of SQL queries (≤ 8 for v1, including the AVG query).
-
-## Workflow Invariants (NORMATIVE assumption)
-
-- **`endDate` invariant**: A case is considered "open" iff `endDate IS NULL`. The Procest workflow engine MUST set `endDate` whenever a case transitions to a final status (`statusType.isFinal === true`). If a deployment can produce a case in a final status without an `endDate`, the open-case proxy MUST be re-validated by the operator. The `KpiAggregationService` PHP docblock MUST document this invariant. A verification task in tasks.md MUST cover an automated assertion that no `endDate IS NULL` case has a `currentStatus` whose `statusType.isFinal` is `true`.
-
-## Accepted Risks (NORMATIVE)
-
-- **Row-level ACL not applied to case counts in v1.** `openCount`, `overdueCount`, `completedCount`, `newToday`, `statusBreakdown`, and `avgProcessingDays` count every case the user can access at the register-read level. If row-level ACL ever becomes enabled in Procest, totals will over-count relative to what the user can actually see. This matches today's client-side `fetchCollection('case')` behaviour, so v1 introduces no regression. A follow-up change is required if row-level ACL lands in Procest.
diff --git a/openspec/changes/admin-settings/specs/admin-settings/spec.md b/openspec/changes/admin-settings/specs/admin-settings/spec.md
deleted file mode 100644
index ea0886d3..00000000
--- a/openspec/changes/admin-settings/specs/admin-settings/spec.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Delta: admin-settings
-
-## Changes from base spec
-
-### REQ-ADMIN-009 (IMPLEMENTED)
-- Created ResultsTab.vue with result type CRUD
-- Archival action (retain/destroy) via radio buttons
-- Retention period input in ISO 8601 format
-- Human-readable period display (e.g., "20 years")
-
-### REQ-ADMIN-010 (IMPLEMENTED)
-- Created RolesTab.vue with role type CRUD
-- Generic role dropdown with 8 options: initiator, handler, advisor, decision_maker, stakeholder, coordinator, contact, co_initiator
-- Multiple role types can share the same generic role
-
-### REQ-ADMIN-011 (IMPLEMENTED)
-- Created PropertiesTab.vue with property definition CRUD
-- Format dropdown: text, number, date, datetime
-- Max length field (number input)
-- Required at status dropdown populated from case type's status types
-- Optional/required toggle via status selection
-
-### REQ-ADMIN-004 (ENHANCED)
-- CaseTypeDetail tabs expanded from 2 (General, Statuses) to 5 (General, Statuses, Results, Roles, Properties)
diff --git a/openspec/changes/ai-assisted-processing/specs/ai-assisted-processing/spec.md b/openspec/changes/ai-assisted-processing/specs/ai-assisted-processing/spec.md
deleted file mode 100644
index a459c45f..00000000
--- a/openspec/changes/ai-assisted-processing/specs/ai-assisted-processing/spec.md
+++ /dev/null
@@ -1,331 +0,0 @@
----
-status: implemented
----
-# ai-assisted-processing Specification
-
-## Purpose
-Enable AI-assisted case processing in Procest using the existing MCP (Model Context Protocol) integration. AI capabilities include document classification and data extraction, knowledge base Q&A (RAG) for case worker support, decision support suggestions, case routing recommendations, and auto-summarization. AI assists human case workers rather than making autonomous decisions -- every AI suggestion requires human confirmation.
-
-## Context
-AI-assisted processing is an emerging capability in modern case management platforms. Flowable's Agentic AI integrates orchestrator, knowledge, document, and utility AI agents directly into the CMMN engine with full audit trails. Our MCP integration with n8n provides the foundation for similar capabilities without requiring a proprietary AI engine -- n8n workflows orchestrate AI model calls while Procest surfaces the results in the case worker UI. This spec defines how AI capabilities surface in Procest, following the human-in-the-loop principle mandated by Dutch government AI governance (Algoritmeregister).
-
-## Requirements
-
-### Requirement 1: Document classification with zaaktype and metadata suggestion
-When documents are uploaded to a case or arrive unclassified, AI MUST suggest classification with confidence scoring.
-
-#### Scenario 1.1: Classify incoming document by type
-- GIVEN a PDF document uploaded to case `zaak-1`
-- WHEN the case worker clicks "AI classificeren" on the document in the case detail view
-- THEN the system MUST send the document content to the configured AI model via an n8n workflow triggered through MCP
-- AND return a suggested `documentType` (from the case type's configured document types) with a confidence score (0.0-1.0)
-- AND return suggested metadata fields (date, sender, subject) extracted from the document content
-- AND the case worker MUST confirm or modify the suggestion before it is applied to the `caseDocument` record
-
-#### Scenario 1.2: Route unclassified document to correct case
-- GIVEN a document arrives via OpenConnector without case linkage
-- WHEN the case worker triggers "AI routeren" on the document
-- THEN the AI MUST analyze the document content and compare it against active cases in the register
-- AND return up to 5 candidate cases ranked by relevance score
-- AND each candidate MUST show the case title, identifier, zaaktype, and relevance explanation
-- AND the case worker MUST select the correct case to link the document
-
-#### Scenario 1.3: Auto-suggest classification on upload
-- GIVEN AI auto-classification is enabled in app settings
-- WHEN a document is uploaded to a case
-- THEN the system MUST automatically trigger classification in the background
-- AND display the suggestion as a dismissable banner on the document: "AI suggests: Bezwaarschrift (87% confidence)"
-- AND the suggestion MUST expire after 7 days if not acted upon
-
-#### Scenario 1.4: Classification model selection per zaaktype
-- GIVEN different zaaktypes may benefit from different classification prompts
-- WHEN an admin configures AI classification for a specific zaaktype
-- THEN they MUST be able to specify a custom system prompt that includes zaaktype-specific document type descriptions
-- AND the default prompt MUST use the document type names and descriptions from the zaaktype configuration
-
-#### Scenario 1.5: Classification handles non-text documents
-- GIVEN a scanned image document (TIFF/JPEG) is uploaded
-- WHEN the case worker triggers "AI classificeren"
-- THEN the system MUST first perform OCR (via Docudesk or the AI model's vision capabilities)
-- AND then classify the extracted text
-- AND indicate to the case worker that OCR was used with the OCR confidence level
-
-### Requirement 2: Data extraction from documents to case fields
-AI MUST read document content and suggest field values for the case or related objects.
-
-#### Scenario 2.1: Extract structured data from application document
-- GIVEN a permit application PDF attached to case `zaak-1` with zaaktype `omgevingsvergunning`
-- WHEN the case worker triggers "AI extractie"
-- THEN the system MUST extract key-value pairs from the document content
-- AND map them to the case's property definitions (e.g., `applicant_name`, `address`, `requested_activity`)
-- AND present the extracted values as pre-filled suggestions in the case form (editable, not auto-saved)
-- AND the case worker MUST review and confirm each extracted value before it is saved
-
-#### Scenario 2.2: Confidence indicators per extracted field
-- GIVEN AI extracts 10 fields from a document
-- WHEN presenting results to the case worker
-- THEN each field MUST show a confidence indicator: high (>0.85), medium (0.60-0.85), low (<0.60)
-- AND low-confidence fields MUST be visually highlighted with an orange border for careful review
-- AND the case worker MUST explicitly confirm low-confidence fields (not just bulk-accept)
-
-#### Scenario 2.3: Extraction from multiple documents
-- GIVEN a case with 5 uploaded documents
-- WHEN the case worker triggers "AI extractie" on the case level (not a single document)
-- THEN the AI MUST analyze all documents and merge extracted fields, preferring the highest-confidence value when conflicts occur
-- AND conflicting values MUST be flagged for manual resolution with source document references
-
-#### Scenario 2.4: Extraction template per zaaktype
-- GIVEN a zaaktype with specific property definitions
-- WHEN AI extraction runs
-- THEN the extraction prompt MUST include the zaaktype's property definitions as the target schema
-- AND only extract fields that match defined properties (no arbitrary key-value extraction)
-
-#### Scenario 2.5: Extraction preserves source reference
-- GIVEN an extracted field value "Jan de Vries" for property "applicant_name"
-- THEN the extraction result MUST include the source document name, page number, and surrounding text snippet
-- AND this reference MUST be viewable by the case worker when hovering over the extracted value
-
-### Requirement 3: Knowledge base Q&A (RAG) for case worker support
-RAG-based Q&A MUST allow case workers to ask questions about policies, procedures, and regulations relevant to their case.
-
-#### Scenario 3.1: Ask a policy question in case context
-- GIVEN a case worker handling an `omgevingsvergunning` case
-- WHEN they open the AI assistant panel and ask "Wat zijn de maximale bouwhoogtes in zone B?"
-- THEN the system MUST search relevant policy documents in the knowledge base via RAG
-- AND return an answer with source citations (document name, page/section, direct quote)
-- AND the answer MUST be scoped to the municipality's own policy documents first, then national regulations
-
-#### Scenario 3.2: No answer available -- refuse to hallucinate
-- GIVEN a case worker asks a question with no relevant documents in the knowledge base
-- THEN the system MUST respond with "Geen relevante informatie gevonden in de kennisbank"
-- AND suggest: "Voeg relevante beleidsdocumenten toe aan de kennisbank"
-- AND MUST NOT generate a plausible-sounding but unsourced answer
-
-#### Scenario 3.3: Knowledge base population from case documents
-- GIVEN an admin enables "auto-index case documents" for a zaaktype
-- WHEN documents are uploaded to cases of that type
-- THEN policy documents (beleidsstukken, verordeningen) MUST be automatically indexed in the RAG knowledge base
-- AND case-specific documents (citizen applications, personal data) MUST NOT be indexed unless explicitly marked as policy documents
-
-#### Scenario 3.4: Context-aware answers
-- GIVEN a case worker asks "Hoeveel tijd heb ik nog voor een besluit?"
-- WHEN the AI assistant has access to the current case's deadline information
-- THEN the answer MUST include the specific deadline date and days remaining from the case data
-- AND cite the relevant legal basis for the deadline (e.g., WOO Art. 4.4 for WOO cases)
-
-#### Scenario 3.5: Conversation history within case
-- GIVEN a case worker has asked 3 questions in the AI assistant for case `zaak-1`
-- WHEN they ask a follow-up question
-- THEN the system MUST include the previous questions and answers as conversation context
-- AND the conversation history MUST be stored on the case for audit and handover purposes
-
-### Requirement 4: Decision support and next-action suggestions
-AI MUST analyze case state and history to suggest what the case worker should do next.
-
-#### Scenario 4.1: Suggest next step based on case state
-- GIVEN case `zaak-1` has status `intake_complete` and all required documents are uploaded
-- WHEN the case worker opens the case
-- THEN the AI assistant panel MAY show: "Alle intake documenten zijn aanwezig. Overweeg de zaak naar beoordelingsfase te verplaatsen."
-- AND the suggestion MUST be dismissable and non-blocking
-- AND the suggestion MUST include a one-click action to execute the suggested step
-
-#### Scenario 4.2: Flag potential deadline issues
-- GIVEN case `zaak-1` has a bezwaartermijn ending in 3 days and no decision recorded
-- WHEN the case worker opens the case
-- THEN the AI MUST flag: "Bezwaartermijn verloopt over 3 dagen -- besluit is mogelijk nodig"
-- AND the flag MUST appear as a prominent warning in the case detail header
-- AND link to the relevant deadline information in the `DeadlinePanel`
-
-#### Scenario 4.3: Summarize case for handover
-- GIVEN a case worker requests "AI samenvatting" for case `zaak-1`
-- WHEN the AI processes the case data (status history, documents, notes, tasks)
-- THEN it MUST generate a structured summary with: current status, key dates, open tasks, recent activity, and recommended next steps
-- AND the summary MUST be savable as a case note in the `ActivityTimeline`
-
-#### Scenario 4.4: Similar case detection
-- GIVEN a new case is created with certain properties (zaaktype, subject, applicant)
-- WHEN the case worker triggers "Vergelijkbare zaken zoeken"
-- THEN the AI MUST search for similar completed cases based on content similarity
-- AND return up to 5 similar cases with their outcomes (resultaat) and processing time
-- AND the case worker MUST be able to view the similar cases for reference
-
-#### Scenario 4.5: Workload balancing suggestions
-- GIVEN a team has 50 active cases distributed across 5 case workers
-- WHEN a manager views the team dashboard
-- THEN the AI MAY suggest workload redistribution: "Medewerker A heeft 15 zaken (3 urgent), medewerker B heeft 5. Overweeg herverdeling."
-- AND the suggestion MUST be based on case count, urgency, and estimated complexity
-
-### Requirement 5: Case auto-summarization
-AI MUST generate human-readable summaries of case content for quick orientation.
-
-#### Scenario 5.1: Auto-summary on case open
-- GIVEN a case with more than 5 documents and 10 timeline entries
-- WHEN the case worker opens the case for the first time (or after 7+ days)
-- THEN the system MAY display an auto-generated summary panel at the top of the case detail
-- AND the summary MUST cover: what the case is about, current status, key dates, and what needs attention
-
-#### Scenario 5.2: Document summary
-- GIVEN a 25-page policy document attached to a case
-- WHEN the case worker clicks "AI samenvatting" on the document
-- THEN the system MUST generate a 3-5 sentence summary of the document
-- AND display it inline below the document title in the case document list
-
-#### Scenario 5.3: Timeline summary for long-running cases
-- GIVEN a case with 50+ timeline entries spanning 6 months
-- WHEN the case worker clicks "Tijdlijn samenvatting"
-- THEN the AI MUST generate a chronological summary highlighting key events (status changes, decisions, escalations)
-- AND the summary MUST be displayable as a collapsed panel above the full timeline
-
-### Requirement 6: AI interaction audit trail
-Every AI suggestion, acceptance, and rejection MUST be recorded for accountability and Algoritmeregister compliance.
-
-#### Scenario 6.1: Audit trail for accepted suggestion
-- GIVEN AI suggests `documentType: "bezwaarschrift"` for a document with confidence 0.92
-- WHEN the case worker accepts the suggestion
-- THEN an audit entry MUST be created in the case's activity log with:
- - `type`: `ai.suggestion.accepted`
- - `model`: the AI model identifier (e.g., "ollama/llama3.1")
- - `suggestion`: the original suggestion payload
- - `confidence`: 0.92
- - `user`: the case worker who accepted
- - `timestamp`: ISO 8601 datetime
-
-#### Scenario 6.2: Audit trail for rejected suggestion
-- GIVEN AI suggests routing a document to case `zaak-1`
-- WHEN the case worker rejects the suggestion and manually assigns to `zaak-2`
-- THEN an audit entry MUST record:
- - `type`: `ai.suggestion.rejected`
- - `suggestion`: `{"case": "zaak-1", "confidence": 0.78}`
- - `actual`: `{"case": "zaak-2"}`
- - `reason`: optional free-text reason from the case worker
- - `user`: the case worker
-
-#### Scenario 6.3: Audit trail for RAG Q&A
-- GIVEN a case worker asks a question via the knowledge base
-- THEN an audit entry MUST record the question, the answer, the source documents cited, and the model used
-- AND this MUST be queryable for Algoritmeregister reporting
-
-#### Scenario 6.4: Aggregate AI usage reporting
-- GIVEN an admin requests AI usage statistics
-- THEN the system MUST provide: total suggestions made, acceptance rate, rejection rate, average confidence scores, most common suggestion types, and per-model usage breakdown
-
-#### Scenario 6.5: Audit entries are immutable
-- GIVEN an AI audit trail entry has been created
-- THEN it MUST NOT be editable or deletable by any user
-- AND it MUST be retained for at least the case's archival retention period
-
-### Requirement 7: AI case routing recommendations
-AI MUST suggest the best case worker or team for incoming cases based on expertise and workload.
-
-#### Scenario 7.1: Route new case to specialist
-- GIVEN a new WOO case arrives via intake
-- WHEN the case is created and AI routing is enabled
-- THEN the AI MUST analyze the case subject and recommend a case worker with WOO expertise
-- AND the recommendation MUST factor in current workload (number of active cases per worker)
-- AND the case worker MUST confirm assignment
-
-#### Scenario 7.2: Route based on geographic area
-- GIVEN a case related to a specific neighborhood or address
-- WHEN AI routing analyzes the case
-- THEN it MUST consider geographic assignment rules (wijkteam, gebiedsteam) if configured
-- AND suggest the case worker responsible for that area
-
-#### Scenario 7.3: Escalation routing
-- GIVEN a case that has been stalled for more than its expected processing time
-- WHEN the AI detects the stall during periodic analysis
-- THEN it MUST suggest escalation to a senior case worker or manager
-- AND include the stall duration and potential reasons in the suggestion
-
-### Requirement 8: AI features opt-in and configuration
-AI features MUST be individually toggleable per municipality, with support for local and cloud AI models.
-
-#### Scenario 8.1: Disable all AI features
-- GIVEN an admin navigates to Procest app settings
-- WHEN they toggle "AI-ondersteuning" to disabled
-- THEN no AI buttons, panels, or suggestions MUST appear in the case worker UI
-- AND no case data MUST be sent to any AI model
-- AND the toggle MUST take effect immediately without requiring app restart
-
-#### Scenario 8.2: Configure local AI model (Ollama)
-- GIVEN AI features are enabled
-- WHEN an admin configures the AI model as a local Ollama instance (e.g., `http://ollama:11434`)
-- THEN all AI requests MUST be routed to the local model
-- AND the admin MUST be able to select the specific model (e.g., llama3.1, mistral, qwen2.5)
-- AND document content MUST NOT leave the Nextcloud server network
-
-#### Scenario 8.3: Configure cloud AI model
-- GIVEN AI features are enabled
-- WHEN an admin configures an external AI model (OpenAI, Azure OpenAI, Anthropic)
-- THEN the system MUST display a warning: "Zaakgegevens worden naar een externe dienst verzonden. Zorg dat dit past binnen uw verwerkingsovereenkomst."
-- AND the admin MUST explicitly acknowledge the privacy implications
-- AND the configuration MUST store the API key securely via Nextcloud's credential store
-
-#### Scenario 8.4: Feature-level toggles
-- GIVEN AI features are globally enabled
-- THEN the admin MUST be able to individually toggle:
- - Document classification (on/off)
- - Data extraction (on/off)
- - Knowledge base Q&A (on/off)
- - Decision support suggestions (on/off)
- - Auto-summarization (on/off)
- - Case routing (on/off)
-- AND each feature MUST work independently
-
-#### Scenario 8.5: AI model health monitoring
-- GIVEN an AI model is configured
-- THEN the settings page MUST show the model connection status (connected/error)
-- AND a "Test verbinding" button MUST send a test prompt and display the response time
-- AND if the model is unreachable, AI features MUST gracefully degrade (hide AI buttons, show "AI niet beschikbaar" on hover)
-
-### Requirement 9: Privacy and data protection for AI processing
-AI processing MUST comply with AVG/GDPR and BIO requirements for government data.
-
-#### Scenario 9.1: Data minimization in AI prompts
-- GIVEN the system sends case data to an AI model for classification
-- THEN only the minimum necessary data MUST be included in the prompt (document content, not full case history)
-- AND BSN, financial data, and health information MUST be stripped from prompts unless explicitly required for the task
-
-#### Scenario 9.2: DPIA requirement tracking
-- GIVEN AI features are enabled for the first time
-- THEN the system MUST display a warning: "AI-verwerking van zaakgegevens vereist een Data Protection Impact Assessment (DPIA)"
-- AND the admin MUST acknowledge this requirement
-- AND the acknowledgement MUST be logged
-
-#### Scenario 9.3: Data retention for AI interactions
-- GIVEN AI interaction data (prompts, responses) is stored for audit purposes
-- THEN the retention period MUST match the case's archival retention period
-- AND when a case is destroyed per retention policy, associated AI audit data MUST also be destroyed
-
-## Dependencies
-- n8n MCP server (for AI workflow orchestration)
-- OpenRegister MCP (for case data access)
-- Ollama or external LLM provider (for AI model inference)
-- Docudesk (for OCR of scanned documents)
-- OpenConnector (for document ingestion from external sources)
-- Nextcloud AI integration (`OCP\TextProcessing`) as potential alternative backend
-
----
-
-### Current Implementation Status
-
-**Not yet implemented.** No AI-related services, controllers, or Vue components exist in the Procest codebase. The MCP integration infrastructure exists at the workspace level (`.mcp.json` with n8n-mcp and OpenRegister MCP), but Procest itself has no AI document classification, data extraction, knowledge base Q&A, or decision support functionality.
-
-**Foundation available:**
-- The n8n MCP server is configured at the workspace level, providing workflow orchestration that could trigger AI pipelines.
-- OpenRegister MCP provides data access that AI tools could query.
-- The `objectStore` pattern (`src/store/modules/object.js`) with `auditTrailsPlugin` provides the audit infrastructure that AI interaction logging would use.
-- `ActivityTimeline.vue` supports activity entries with type, description, user, and date -- extensible for AI audit entries.
-- Nextcloud's `OCP\TextProcessing\IManager` provides a native AI abstraction that could serve as an alternative to direct MCP calls.
-
-**Partial implementations:** None.
-
-### Standards & References
-
-- **MCP (Model Context Protocol)**: Anthropic's standard for LLM tool integration -- the foundation for AI features.
-- **GDPR / AVG**: AI processing of citizen data requires Data Protection Impact Assessment (DPIA), especially for document classification containing PII.
-- **BIO (Baseline Informatiebeveiliging Overheid)**: Government security baseline applies to AI model endpoints and data handling.
-- **Algoritmeregister**: Dutch government requirement to register algorithmic decision-making systems. All AI features that influence case outcomes must be registered.
-- **Common Ground**: AI services should be deployable as Common Ground components (API-first, layered architecture).
-- **WCAG AA**: AI suggestion UI must be accessible, including screen reader announcements for suggestions.
-- **Flowable Agentic AI**: Reference architecture for integrating AI agents into CMMN case management (orchestrator, knowledge, document, utility agents).
-- **CMMN 1.1**: AI suggestions map to SentryEvents that can trigger case plan items.
diff --git a/openspec/changes/appointment-scheduling/specs/appointment-scheduling/spec.md b/openspec/changes/appointment-scheduling/specs/appointment-scheduling/spec.md
deleted file mode 100644
index 6b2bb12c..00000000
--- a/openspec/changes/appointment-scheduling/specs/appointment-scheduling/spec.md
+++ /dev/null
@@ -1,343 +0,0 @@
----
-status: implemented
----
-# appointment-scheduling Specification
-
-## Purpose
-Integrate appointment scheduling (afsprakenbeheer) into Procest case flows for cases that require physical service delivery at a municipal counter (balie). Citizens can book appointments as part of case submission or at any point during case handling. The system integrates with existing municipal appointment backends (Qmatic, JCC Afspraken) via a plugin architecture, and supports self-service cancellation and modification.
-
-## Context
-In Dutch municipalities, balie appointments are standard for services like passport collection, marriage registration, and permit discussions. Open-Formulieren implements appointment scheduling as part of form submissions with integration plugins for JCC and Qmatic -- product/location/timeslot selection during intake with configurable contact details. This is the reference model. Procest extends this by embedding appointments into the case lifecycle, making appointment status visible in case context, and supporting both citizen self-service and case worker-initiated scheduling.
-
-## Requirements
-
-### Requirement 1: Appointments bookable as part of case flow
-Case workers or citizens MUST be able to create appointments linked to a case at any point during the case lifecycle.
-
-#### Scenario 1.1: Book appointment during case intake
-- GIVEN a citizen is submitting a `paspoort_aanvraag` case
-- AND the zaaktype is configured with `requiresAppointment: true`
-- WHEN the citizen reaches the appointment step in the intake flow
-- THEN the system MUST show:
- - Available products (e.g., "Paspoort ophalen", "Rijbewijs ophalen") filtered by zaaktype configuration
- - Available locations (e.g., "Stadskantoor", "Wijkkantoor Noord") for the selected product
- - Available dates and timeslots for the selected product/location combination
-- AND the citizen MUST select a timeslot to proceed with case submission
-- AND the appointment MUST be automatically linked to the created case
-
-#### Scenario 1.2: Book appointment from case detail view
-- GIVEN case `zaak-1` is in progress and needs a physical meeting
-- WHEN a case worker clicks "Plan afspraak" in the `CaseDetail.vue` header actions
-- THEN an appointment booking dialog MUST appear with:
- - Product pre-selected based on the zaaktype (editable)
- - Location dropdown with configured municipal locations
- - Date picker showing available dates
- - Timeslot grid for the selected date
-- AND the appointment MUST be linked to `zaak-1` after booking
-- AND an activity entry MUST appear in the `ActivityTimeline`
-
-#### Scenario 1.3: Multiple appointments per case
-- GIVEN case `zaak-1` already has an appointment for document submission
-- WHEN the case worker books a second appointment for document collection
-- THEN both appointments MUST be listed in the case's appointment section
-- AND each appointment MUST have its own status and lifecycle
-
-#### Scenario 1.4: Appointment as required task
-- GIVEN a zaaktype configured with an appointment required at status "Ophalen"
-- WHEN the case reaches the "Ophalen" status
-- THEN a task MUST be auto-created: "Plan afspraak voor ophalen"
-- AND the case MUST NOT be advanceable to the next status until the appointment is booked
-
-#### Scenario 1.5: Appointment links to case participants
-- GIVEN a case with a linked citizen (role: initiator, with BSN and contact details)
-- WHEN booking an appointment
-- THEN the citizen's name, phone number, and email MUST be pre-filled from the case role data
-- AND the case worker MUST be able to override the contact details (e.g., if someone else will attend)
-
-### Requirement 2: Pluggable appointment backend architecture
-Different municipalities use different appointment systems; the integration MUST be pluggable.
-
-#### Scenario 2.1: JCC Afspraken integration
-- GIVEN the municipality uses JCC Afspraken
-- AND the JCC plugin is configured in Procest settings with: API URL, API key, and organization ID
-- WHEN a timeslot query is made for product "Paspoort ophalen" at location "Stadskantoor"
-- THEN the plugin MUST call the JCC API endpoint `/openapi/v1/beschikbaarheid` to retrieve available slots
-- AND booking MUST call JCC's `/openapi/v1/afspraken` to create the appointment
-- AND the JCC appointment ID MUST be stored on the Procest appointment record for sync
-- AND cancellation MUST call JCC's delete endpoint to cancel in both systems
-
-#### Scenario 2.2: Qmatic Orchestra integration
-- GIVEN the municipality uses Qmatic Orchestra
-- AND the Qmatic plugin is configured with: base URL, API key, and branch ID
-- WHEN a timeslot query is made
-- THEN the plugin MUST call the Qmatic REST API (`/rest/servicepoint/branches/{id}/dates/{date}/times`)
-- AND booking MUST create the appointment in Qmatic
-- AND the Qmatic appointment reference MUST be stored on the Procest record
-
-#### Scenario 2.3: Fallback manual scheduling (no backend)
-- GIVEN no appointment backend is configured
-- WHEN a case worker creates an appointment
-- THEN the appointment MUST be stored locally in OpenRegister as an appointment object
-- AND a Nextcloud Calendar event MUST be created via `OCP\Calendar\IManager`
-- AND the calendar event MUST include the case reference, citizen name, product, and location
-
-#### Scenario 2.4: Plugin registration via OpenConnector
-- GIVEN the plugin architecture uses OpenConnector as the API adapter layer
-- WHEN an admin configures a new appointment backend
-- THEN they MUST select the backend type (JCC/Qmatic/Custom) and configure the connection via OpenConnector source settings
-- AND the system MUST validate the connection with a test call before saving
-
-#### Scenario 2.5: Backend failover handling
-- GIVEN the JCC API returns a 503 Service Unavailable error
-- WHEN a case worker attempts to book an appointment
-- THEN the system MUST display: "Afsprakensysteem tijdelijk niet beschikbaar. Probeer het later opnieuw."
-- AND the error MUST be logged with timestamp and response details
-- AND the system MUST NOT fall back to manual scheduling unless explicitly configured
-
-### Requirement 3: Citizen self-service appointment management
-Citizens MUST be able to cancel, reschedule, and view their appointments without contacting the municipality.
-
-#### Scenario 3.1: Cancel an appointment via confirmation link
-- GIVEN citizen has appointment `apt-1` for March 25, 2026 at 10:00 at Stadskantoor
-- AND the citizen received a confirmation email with a unique cancellation link
-- WHEN the citizen opens the link and clicks "Annuleren"
-- THEN a confirmation dialog MUST appear: "Weet u zeker dat u uw afspraak op 25 maart om 10:00 wilt annuleren?"
-- AND upon confirmation, the appointment MUST be cancelled in both Procest and the backend system (JCC/Qmatic)
-- AND a cancellation confirmation MUST be sent (email and/or SMS based on configuration)
-- AND the case `ActivityTimeline` MUST record: "Afspraak geannuleerd door burger"
-
-#### Scenario 3.2: Reschedule an appointment
-- GIVEN citizen has appointment `apt-1` for March 25 at 10:00
-- WHEN the citizen accesses their appointment via the confirmation link and clicks "Verzetten"
-- THEN available alternative timeslots MUST be shown for the same product and location
-- AND selecting a new slot MUST atomically cancel the old appointment and book the new one
-- AND a new confirmation MUST be sent with updated date/time/location
-
-#### Scenario 3.3: View appointment details
-- GIVEN a citizen accesses their appointment link
-- THEN the page MUST show: date, time, location (with address and map link), product, what to bring, and the case reference number
-- AND provide buttons for "Annuleren" and "Verzetten"
-- AND the page MUST NOT require authentication (token-based access)
-
-#### Scenario 3.4: Cancellation deadline enforcement
-- GIVEN the municipality configures a minimum cancellation notice of 24 hours
-- WHEN a citizen attempts to cancel appointment `apt-1` that starts in 4 hours
-- THEN the system MUST display: "Annuleren is niet meer mogelijk. Neem contact op met de gemeente."
-- AND provide a phone number or contact form link
-
-#### Scenario 3.5: Self-service link expiration
-- GIVEN appointment `apt-1` was scheduled for March 25 at 10:00
-- AND today is March 26 (appointment has passed)
-- WHEN the citizen accesses the confirmation link
-- THEN the page MUST show: "Deze afspraak heeft plaatsgevonden op 25 maart 2026"
-- AND cancellation and rescheduling MUST be disabled
-
-### Requirement 4: Appointment lifecycle and reminder notifications
-Appointments MUST track status through their lifecycle with automated reminders to reduce no-shows.
-
-#### Scenario 4.1: Appointment confirmation notification
-- GIVEN a citizen books appointment `apt-1` for March 25 at 10:00 at Stadskantoor
-- THEN a confirmation MUST be sent (configurable: email, SMS, or both) containing:
- - Date, time, and location with address
- - Product name (what the appointment is for)
- - What to bring (linked to zaaktype `requiresDocuments` configuration)
- - Cancellation/modification link (unique token-based URL)
- - Case reference number
-- AND the confirmation MUST be sent via an n8n workflow for template flexibility
-
-#### Scenario 4.2: Reminder notification before appointment
-- GIVEN appointment `apt-1` is scheduled for tomorrow at 10:00
-- WHEN the Nextcloud cron job runs the reminder check
-- THEN a reminder MUST be sent to the citizen via the configured channel
-- AND the reminder interval MUST be configurable per zaaktype (default: 1 day before)
-- AND the reminder MUST include a "not able to make it" link for easy cancellation
-
-#### Scenario 4.3: No-show recording
-- GIVEN appointment `apt-1` was scheduled for 10:00 and the citizen did not appear
-- WHEN the case worker marks the appointment as "Niet verschenen" (no-show)
-- THEN the appointment status MUST change to `niet_verschenen`
-- AND the case `ActivityTimeline` MUST record: "Burger niet verschenen bij afspraak"
-- AND a follow-up task MUST be auto-created: "Contact opnemen na niet-verschijnen" if configured
-
-#### Scenario 4.4: Appointment completed
-- GIVEN appointment `apt-1` took place
-- WHEN the case worker marks it as "Afgerond" (completed)
-- THEN the appointment status MUST change to `afgerond`
-- AND the case timeline MUST record: "Afspraak gehouden: 25 maart 2026, 10:00, Stadskantoor"
-- AND if the zaaktype has a post-appointment status transition configured, the case MUST auto-advance
-
-#### Scenario 4.5: Appointment status lifecycle
-- GIVEN an appointment object in OpenRegister
-- THEN it MUST support the following statuses:
- - `gepland` (initial, after booking)
- - `herinnerd` (after reminder sent)
- - `afgerond` (completed successfully)
- - `niet_verschenen` (no-show)
- - `geannuleerd` (cancelled by citizen or case worker)
- - `verzet` (rescheduled -- old appointment gets this status)
-
-### Requirement 5: Appointment visibility in case context
-Appointment data MUST be visible in the case timeline and case detail view.
-
-#### Scenario 5.1: Appointment section in case detail
-- GIVEN case `zaak-1` has one or more appointments
-- THEN the case detail view MUST show an "Afspraken" section listing all appointments
-- AND each appointment MUST show: date/time, location, product, status, and citizen name
-- AND appointments MUST be ordered by date (upcoming first)
-
-#### Scenario 5.2: Timeline integration
-- GIVEN case `zaak-1` has an appointment lifecycle
-- WHEN viewing the `ActivityTimeline` component
-- THEN the following events MUST appear chronologically:
- - "Afspraak gepland: 25 maart 2026, 10:00, Stadskantoor"
- - "Herinnering verzonden naar burger"
- - "Afspraak gehouden" or "Burger niet verschenen"
-- AND each event MUST include an icon appropriate to its type
-
-#### Scenario 5.3: Appointment in case list overview
-- GIVEN the case list view at `CaseList.vue`
-- THEN cases with upcoming appointments MUST show a calendar icon with the next appointment date
-- AND cases where the citizen was a no-show MUST show a warning indicator
-
-#### Scenario 5.4: Appointment on dashboard
-- GIVEN the Procest dashboard (`Dashboard.vue`)
-- THEN a "Komende afspraken" widget MUST list today's and tomorrow's appointments across all cases assigned to the current user
-- AND each entry MUST link to the case detail
-
-### Requirement 6: Real-time timeslot availability
-Shown timeslots MUST reflect current availability to prevent double bookings and stale data.
-
-#### Scenario 6.1: Live availability query
-- GIVEN a citizen or case worker is browsing available timeslots
-- WHEN they select a date
-- THEN the system MUST query the appointment backend in real-time (not cached) for that date
-- AND display available slots with capacity indicators (if the backend provides capacity data)
-
-#### Scenario 6.2: Concurrent booking prevention
-- GIVEN two citizens view the same timeslot as available
-- WHEN both attempt to book it simultaneously
-- THEN only one booking MUST succeed (the backend system handles atomicity)
-- AND the other MUST receive: "Dit tijdslot is zojuist geboekt. Kies een ander tijdslot."
-- AND the timeslot grid MUST refresh to show updated availability
-
-#### Scenario 6.3: Timeslot expiration during booking
-- GIVEN a citizen has been on the booking page for 15 minutes without completing
-- THEN the system MUST display: "Beschikbaarheid kan gewijzigd zijn. Vernieuw de tijdsloten."
-- AND provide a refresh button to reload current availability
-
-#### Scenario 6.4: Availability filtered by capacity
-- GIVEN a location has 3 service desks (balies) available
-- AND 2 are already booked for the 10:00-10:15 slot
-- THEN the slot MUST still show as available (1 remaining)
-- AND when all 3 are booked, the slot MUST show as unavailable
-
-### Requirement 7: Product and location configuration
-Administrators MUST be able to configure which products and locations are available for appointment booking.
-
-#### Scenario 7.1: Configure products per zaaktype
-- GIVEN the admin is editing a zaaktype in `CaseTypeDetail.vue`
-- THEN a "Products" tab MUST allow adding appointment products
-- AND each product MUST have: name, description, estimated duration (minutes), and backend product ID (for JCC/Qmatic mapping)
-- AND products MUST be linkable to specific zaaktype statuses (e.g., "Paspoort ophalen" only available at status "Ophalen")
-
-#### Scenario 7.2: Configure locations
-- GIVEN the admin navigates to appointment settings
-- THEN they MUST be able to manage locations with: name, address, phone number, opening hours, and backend location ID
-- AND locations MUST be filterable by which products they offer
-
-#### Scenario 7.3: Location-specific availability rules
-- GIVEN location "Wijkkantoor Noord" is only open Tuesday through Thursday
-- WHEN a citizen selects this location
-- THEN only Tuesday, Wednesday, and Thursday dates MUST be shown in the date picker
-- AND the opening hours MUST be configured per location in the admin settings
-
-#### Scenario 7.4: Seasonal closures and holidays
-- GIVEN the municipality configures holidays and closure dates
-- THEN those dates MUST be excluded from appointment availability
-- AND existing appointments on newly added closure dates MUST be flagged for rescheduling
-
-### Requirement 8: Appointment data model in OpenRegister
-Appointments MUST be stored as OpenRegister objects with a defined schema.
-
-#### Scenario 8.1: Appointment schema definition
-- GIVEN the Procest register configuration
-- THEN an `appointment` schema MUST be defined with fields:
- - `id` (UUID, auto-generated)
- - `caseId` (reference to case)
- - `citizenName` (string)
- - `citizenEmail` (string)
- - `citizenPhone` (string)
- - `product` (string, from configured products)
- - `location` (string, from configured locations)
- - `dateTime` (ISO 8601 datetime)
- - `duration` (integer, minutes)
- - `status` (enum: gepland/herinnerd/afgerond/niet_verschenen/geannuleerd/verzet)
- - `externalId` (string, JCC/Qmatic reference)
- - `selfServiceToken` (string, unique token for citizen access)
- - `notes` (text, case worker notes)
- - `bookedBy` (string, user who created the booking)
-
-#### Scenario 8.2: Appointment linked to case via caseObject
-- GIVEN an appointment is created for case `zaak-1`
-- THEN a `caseObject` record MUST link the appointment to the case
-- AND querying the case's objects MUST include the appointment
-
-#### Scenario 8.3: Appointment history preserved
-- GIVEN appointment `apt-1` is rescheduled from March 25 to March 28
-- THEN the original appointment MUST be preserved with status `verzet`
-- AND a new appointment MUST be created with the new date and status `gepland`
-- AND both MUST be linked to the same case
-
-### Requirement 9: Notification channel configuration
-Appointment notifications MUST support multiple channels with per-municipality configuration.
-
-#### Scenario 9.1: Email notifications via n8n
-- GIVEN the municipality has email notifications configured
-- WHEN an appointment is booked
-- THEN the confirmation email MUST be sent via an n8n workflow
-- AND the email template MUST be customizable by the municipality (HTML template in n8n)
-
-#### Scenario 9.2: SMS notifications
-- GIVEN the municipality has SMS notifications enabled (via a configured SMS gateway in OpenConnector)
-- WHEN an appointment reminder is triggered
-- THEN an SMS MUST be sent with a short message: "Herinnering: uw afspraak morgen om 10:00 bij Stadskantoor. Niet kunnen komen? [link]"
-
-#### Scenario 9.3: Notification preferences per citizen
-- GIVEN a citizen has specified their notification preference during booking (email, SMS, or both)
-- THEN notifications MUST only be sent via the selected channel(s)
-- AND the preference MUST be stored on the appointment record
-
-## Dependencies
-- OpenRegister (for appointment data storage)
-- OpenConnector (for JCC/Qmatic API adapters and SMS gateway)
-- Nextcloud Calendar (`OCP\Calendar\IManager`) for fallback calendar events
-- n8n (for notification workflow orchestration)
-- Pipelinq (sister app -- appointments booked during CRM interactions may be linked to cases)
-- Mijn Overheid integration (appointment status as case status update)
-
----
-
-### Current Implementation Status
-
-**Not yet implemented.** No appointment-related schemas, controllers, services, or Vue components exist in the Procest codebase. The `procest_register.json` configuration does not include an appointment schema.
-
-**Foundation available:**
-- Case detail view (`src/views/cases/CaseDetail.vue`) provides the integration point where a "Plan afspraak" button would be added in the header actions.
-- Activity timeline component (`src/views/cases/components/ActivityTimeline.vue`) could display appointment events.
-- `DeadlinePanel.vue` shows that date-based tracking UI patterns are established.
-- OpenConnector (external dependency) could host JCC/Qmatic API adapters.
-- The task management infrastructure (`src/views/tasks/`) could model appointment scheduling as a task type.
-- `NotificatieService.php` provides notification infrastructure.
-- n8n MCP tools can orchestrate notification workflows.
-
-**Partial implementations:** None.
-
-### Standards & References
-
-- **VNG GEMMA Referentiearchitectuur**: Afsprakenbeheer is a recognized component in the GEMMA zaakgericht werken reference architecture.
-- **JCC Afspraken API**: Proprietary API for municipal appointment scheduling (widely used in Dutch municipalities). OpenAPI v1 specification.
-- **Qmatic Orchestra REST API**: Standard integration for queue management and appointment booking.
-- **Open-Formulieren Appointment Plugin Architecture**: Reference implementation for pluggable appointment backends (JCC, Qmatic) with product/location/timeslot selection model.
-- **WCAG AA**: Appointment booking UI must be accessible, including date/time pickers that work with keyboard and screen readers.
-- **BRP (Basisregistratie Personen)**: Citizen identification for appointment linking via BSN.
-- **Nextcloud Calendar IManager**: OCP interface for creating calendar events as fallback appointment tracking.
diff --git a/openspec/changes/add-server-side-kpi-aggregation/builds/build.json b/openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/builds/build.json
similarity index 100%
rename from openspec/changes/add-server-side-kpi-aggregation/builds/build.json
rename to openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/builds/build.json
diff --git a/openspec/changes/add-server-side-kpi-aggregation/design.md b/openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/design.md
similarity index 100%
rename from openspec/changes/add-server-side-kpi-aggregation/design.md
rename to openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/design.md
diff --git a/openspec/changes/add-server-side-kpi-aggregation/proposal.md b/openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/proposal.md
similarity index 100%
rename from openspec/changes/add-server-side-kpi-aggregation/proposal.md
rename to openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/specs/kpi-aggregation/spec.md b/openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/specs/kpi-aggregation/spec.md
new file mode 100644
index 00000000..b0e4ea29
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/specs/kpi-aggregation/spec.md
@@ -0,0 +1,211 @@
+# Delta: add-server-side-kpi-aggregation
+
+Capability: `kpi-aggregation` (new)
+Affects canonical spec: `openspec/specs/dashboard/spec.md`
+
+## ADDED Requirements
+
+### Requirement: REQ-KPI-001 — KPI Aggregation Endpoint [V1]
+
+The system MUST expose a `GET /index.php/apps/procest/api/dashboard/kpis` endpoint that returns pre-computed dashboard KPI values as a JSON object.
+
+**Feature tier**: V1
+
+#### Scenario: Endpoint returns stable JSON shape
+
+- **GIVEN** an authenticated user sends `GET /index.php/apps/procest/api/dashboard/kpis`
+- **THEN** the response MUST have status code 200
+- **AND** the response MUST have `Content-Type: application/json`
+- **AND** the body MUST contain all of the following fields with their types:
+ - `openCount` (integer ≥ 0) — count of cases with no `endDate` (not yet closed)
+ - `newToday` (integer ≥ 0) — count of cases whose `startDate` matches today's date
+ - `overdueCount` (integer ≥ 0) — count of open cases where `deadline` < today
+ - `completedCount` (integer ≥ 0) — count of cases whose `endDate` falls in the current calendar month
+ - `taskCount` (integer ≥ 0) — count of tasks assigned to the current user with status `available` or `active`
+ - `tasksDueToday` (integer ≥ 0) — count of those tasks whose `dueDate` matches today
+ - `statusBreakdown` (array of `{status: string, count: integer}`) — open case counts grouped by status value
+ - `avgProcessingDays` (number ≥ 0, or `null` if no completed cases this month) — average days between `startDate` and `endDate` for cases completed in the current calendar month, computed via `AVG(DATEDIFF(endDate, startDate))`
+ - `computedAt` (ISO 8601 datetime string) — when the DB queries that produced this result were executed
+ - `cacheHit` (boolean) — whether the response was served from cache
+
+#### Scenario: Endpoint requires authentication
+
+- **GIVEN** an unauthenticated request to `GET /index.php/apps/procest/api/dashboard/kpis`
+- **THEN** the response MUST have status code 401
+- **AND** no KPI data MUST be returned
+
+#### Scenario: Zero values when no data exists
+
+- **GIVEN** Procest is freshly installed with no cases or tasks
+- **WHEN** an authenticated user requests `GET /api/dashboard/kpis`
+- **THEN** all integer fields MUST be `0`
+- **AND** `statusBreakdown` MUST be `[]`
+- **AND** the response MUST have status code 200 (not an error)
+
+#### Scenario: Error resilience
+
+- **GIVEN** the database is temporarily unavailable
+- **WHEN** the endpoint is called
+- **THEN** the response MUST have status code 500
+- **AND** the response body MUST contain an `error` field with a descriptive message
+- **AND** no partial or misleading data MUST be returned
+
+---
+
+### Requirement: REQ-KPI-002 — DB-Side Aggregation [V1]
+
+All KPI counts MUST be computed by the database engine using `COUNT(*)`, `GROUP BY`, and filter predicates — never by loading object arrays into PHP and counting them in memory.
+
+**Feature tier**: V1
+
+#### Scenario: Correct count at scale
+
+- **GIVEN** 5,000 cases exist in the Procest register
+- **AND** all 5,000 are open (no `endDate`)
+- **WHEN** `GET /api/dashboard/kpis` is called
+- **THEN** `openCount` MUST be `5000`
+- **AND** the response time MUST be under 500ms
+- **AND** no `_limit` cap MUST silently truncate the count
+
+#### Scenario: No PHP-side array iteration for counts
+
+- **GIVEN** a code audit of `KpiAggregationService`
+- **THEN** it MUST NOT load case or task objects into PHP arrays for the purpose of counting
+- **AND** it MUST use `IDBConnection::getQueryBuilder()` with `COUNT(*)` or `selectAlias($qb->func()->count(...), 'cnt')`
+
+---
+
+### Requirement: REQ-KPI-003 — Per-User Cache [V1]
+
+The endpoint MUST cache results per authenticated user with a 60-second TTL via Nextcloud's `ICacheFactory`.
+
+**Feature tier**: V1
+
+#### Scenario: Cache hit within TTL
+
+- **GIVEN** user "jan" called `GET /api/dashboard/kpis` 30 seconds ago
+- **WHEN** user "jan" calls the endpoint again
+- **THEN** the response MUST be served from cache (no DB queries run)
+- **AND** `cacheHit` in the response MUST be `true`
+- **AND** the `computedAt` timestamp MUST match the original computation time (not "now")
+
+#### Scenario: Cache miss after TTL expiry
+
+- **GIVEN** user "jan" called the endpoint 61 seconds ago
+- **WHEN** user "jan" calls the endpoint again
+- **THEN** the system MUST run fresh DB queries
+- **AND** `cacheHit` MUST be `false`
+- **AND** `computedAt` MUST reflect the new query time
+
+#### Scenario: Cache isolation between users
+
+- **GIVEN** user "jan" has 10 open cases and user "maria" has 5 open cases
+- **WHEN** both call `GET /api/dashboard/kpis` simultaneously
+- **THEN** "jan" MUST receive `openCount: 10` and "maria" MUST receive `openCount: 5`
+- **AND** the cache for "jan" MUST NOT be served to "maria"
+
+#### Scenario: Cache fallback when APCu unavailable
+
+- **GIVEN** APCu is disabled on the server
+- **WHEN** `GET /api/dashboard/kpis` is called
+- **THEN** the endpoint MUST still return correct data
+- **AND** it MUST run DB queries on every request (no error, just uncached)
+
+---
+
+### Requirement: REQ-KPI-004 — Cache Invalidation on Object Changes [V1]
+
+The KPI cache for a user MUST be invalidated (without eager recomputation) when that user performs an object create, update, or delete operation in the Procest register.
+
+**Feature tier**: V1
+
+#### Scenario: Cache invalidated after case creation
+
+- **GIVEN** user "jan"'s KPI cache is populated (`openCount: 10`)
+- **WHEN** "jan" creates a new case (triggering `ObjectCreatedEvent`)
+- **AND** "jan" calls `GET /api/dashboard/kpis`
+- **THEN** the response MUST reflect the newly created case (`openCount: 11`)
+- **AND** `cacheHit` MUST be `false` (fresh computation)
+
+#### Scenario: Cache version bump mechanism
+
+- **GIVEN** user "jan"'s current cache version is `3`
+- **WHEN** an `ObjectUpdatedEvent` fires for a change made by "jan"
+- **THEN** the listener MUST increment the version key to `4`
+- **AND** the old version `3` data MAY remain in the cache store until its TTL expires (it will never be served again because the lookup key has changed)
+
+#### Scenario: Other user's cache unaffected
+
+- **GIVEN** user "jan" creates a case
+- **THEN** user "maria"'s KPI cache version MUST NOT be bumped
+- **AND** maria's next `GET /api/dashboard/kpis` MAY still return cached data
+
+---
+
+### Requirement: REQ-KPI — 005a: Average Processing Days [V1]
+
+The endpoint MUST compute `avgProcessingDays` as the SQL average of date differences between `endDate` and `startDate` for cases whose `endDate` falls in the current calendar month.
+
+**Feature tier**: V1
+
+#### Scenario: avgProcessingDays for current month
+
+- **GIVEN** 4 cases were completed this calendar month with processing durations 3, 5, 7, and 9 days
+- **WHEN** `GET /api/dashboard/kpis` is called
+- **THEN** `avgProcessingDays` MUST equal `6.0` (the SQL `AVG(DATEDIFF(endDate, startDate))` result)
+- **AND** it MUST be computed by the database (`AVG(DATEDIFF(endDate, startDate))`), not by PHP-side iteration
+
+#### Scenario: No completed cases this month
+
+- **GIVEN** no cases have been completed in the current calendar month
+- **WHEN** the endpoint is called
+- **THEN** `avgProcessingDays` MUST be `null` (not `0`, since no data is different from "0 days")
+
+#### Scenario: avgProcessingDays uses endDate IS NOT NULL filter
+
+- **GIVEN** open cases (with `endDate IS NULL`) exist alongside completed cases
+- **THEN** open cases MUST be excluded from the AVG computation
+- **AND** only cases with both `startDate IS NOT NULL` and `endDate IS NOT NULL` MUST be included
+
+---
+
+### Requirement: REQ-KPI-005 — Permission-Scoped Task Counts [V1]
+
+Task KPI counts (`taskCount`, `tasksDueToday`) MUST be scoped to the requesting user's assigned tasks. Case counts are register-wide for v1 (matching current behaviour).
+
+**Feature tier**: V1
+
+#### Scenario: Task count scoped to assignee
+
+- **GIVEN** user "jan" has 3 tasks assigned and user "maria" has 7 tasks assigned
+- **WHEN** "jan" calls `GET /api/dashboard/kpis`
+- **THEN** `taskCount` MUST be `3` (not `10`)
+
+#### Scenario: Case counts are register-wide for v1
+
+- **GIVEN** user "jan" views the dashboard
+- **THEN** `openCount`, `overdueCount`, `completedCount`, and `newToday` reflect all cases in the Procest register, not just Jan's assigned cases
+- **AND** this matches the current client-side behaviour where `fetchCollection('case')` returns all accessible cases
+
+#### Scenario: statusBreakdown is register-wide
+
+- **GIVEN** the register has 20 open cases with 3 different statuses
+- **WHEN** any authenticated user calls `GET /api/dashboard/kpis`
+- **THEN** `statusBreakdown` MUST list all 3 statuses with their respective counts
+
+---
+
+## Performance Requirements (ADDED)
+
+- `GET /api/dashboard/kpis` MUST respond in < 100ms on a cache hit.
+- `GET /api/dashboard/kpis` MUST respond in < 500ms on a cache miss for up to 10,000 objects.
+- Response time MAY exceed 500ms for > 10,000 objects; adding a generated column index on the status JSON field is the recommended mitigation if this threshold is reached in production.
+- The endpoint MUST NOT perform N+1 queries — all counts MUST be computed in a fixed number of SQL queries (≤ 8 for v1, including the AVG query).
+
+## Workflow Invariants (NORMATIVE assumption)
+
+- **`endDate` invariant**: A case is considered "open" iff `endDate IS NULL`. The Procest workflow engine MUST set `endDate` whenever a case transitions to a final status (`statusType.isFinal === true`). If a deployment can produce a case in a final status without an `endDate`, the open-case proxy MUST be re-validated by the operator. The `KpiAggregationService` PHP docblock MUST document this invariant. A verification task in tasks.md MUST cover an automated assertion that no `endDate IS NULL` case has a `currentStatus` whose `statusType.isFinal` is `true`.
+
+## Accepted Risks (NORMATIVE)
+
+- **Row-level ACL not applied to case counts in v1.** `openCount`, `overdueCount`, `completedCount`, `newToday`, `statusBreakdown`, and `avgProcessingDays` count every case the user can access at the register-read level. If row-level ACL ever becomes enabled in Procest, totals will over-count relative to what the user can actually see. This matches today's client-side `fetchCollection('case')` behaviour, so v1 introduces no regression. A follow-up change is required if row-level ACL lands in Procest.
diff --git a/openspec/changes/add-server-side-kpi-aggregation/tasks.md b/openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/tasks.md
similarity index 100%
rename from openspec/changes/add-server-side-kpi-aggregation/tasks.md
rename to openspec/changes/archive/2026-05-11-add-server-side-kpi-aggregation/tasks.md
diff --git a/openspec/changes/admin-settings/builds/build.json b/openspec/changes/archive/2026-05-11-admin-settings/builds/build.json
similarity index 100%
rename from openspec/changes/admin-settings/builds/build.json
rename to openspec/changes/archive/2026-05-11-admin-settings/builds/build.json
diff --git a/openspec/changes/admin-settings/design.md b/openspec/changes/archive/2026-05-11-admin-settings/design.md
similarity index 100%
rename from openspec/changes/admin-settings/design.md
rename to openspec/changes/archive/2026-05-11-admin-settings/design.md
diff --git a/openspec/changes/admin-settings/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-admin-settings/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/admin-settings/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-admin-settings/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/admin-settings/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-admin-settings/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/admin-settings/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-admin-settings/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/admin-settings/proposal.md b/openspec/changes/archive/2026-05-11-admin-settings/proposal.md
similarity index 100%
rename from openspec/changes/admin-settings/proposal.md
rename to openspec/changes/archive/2026-05-11-admin-settings/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-admin-settings/specs/admin-settings/spec.md b/openspec/changes/archive/2026-05-11-admin-settings/specs/admin-settings/spec.md
new file mode 100644
index 00000000..79bbf586
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-admin-settings/specs/admin-settings/spec.md
@@ -0,0 +1,55 @@
+# Delta: admin-settings
+
+## ADDED Requirements
+### Requirement: REQ-ADMIN-009 — Implemented
+The system SHALL satisfy the behaviour described as "REQ-ADMIN-009 — Implemented".
+
+- Created ResultsTab.vue with result type CRUD
+- Archival action (retain/destroy) via radio buttons
+- Retention period input in ISO 8601 format
+- Human-readable period display (e.g., "20 years")
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-ADMIN-010 — Implemented
+The system SHALL satisfy the behaviour described as "REQ-ADMIN-010 — Implemented".
+
+- Created RolesTab.vue with role type CRUD
+- Generic role dropdown with 8 options: initiator, handler, advisor, decision_maker, stakeholder, coordinator, contact, co_initiator
+- Multiple role types can share the same generic role
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-ADMIN-011 — Implemented
+The system SHALL satisfy the behaviour described as "REQ-ADMIN-011 — Implemented".
+
+- Created PropertiesTab.vue with property definition CRUD
+- Format dropdown: text, number, date, datetime
+- Max length field (number input)
+- Required at status dropdown populated from case type's status types
+- Optional/required toggle via status selection
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-ADMIN-004 — Enhanced
+The system SHALL satisfy the behaviour described as "REQ-ADMIN-004 — Enhanced".
+
+- CaseTypeDetail tabs expanded from 2 (General, Statuses) to 5 (General, Statuses, Results, Roles, Properties)
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
diff --git a/openspec/changes/admin-settings/tasks.md b/openspec/changes/archive/2026-05-11-admin-settings/tasks.md
similarity index 100%
rename from openspec/changes/admin-settings/tasks.md
rename to openspec/changes/archive/2026-05-11-admin-settings/tasks.md
diff --git a/openspec/changes/advice-management/.openspec.yaml b/openspec/changes/archive/2026-05-11-advice-management/.openspec.yaml
similarity index 100%
rename from openspec/changes/advice-management/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-advice-management/.openspec.yaml
diff --git a/openspec/changes/advice-management/builds/build.json b/openspec/changes/archive/2026-05-11-advice-management/builds/build.json
similarity index 100%
rename from openspec/changes/advice-management/builds/build.json
rename to openspec/changes/archive/2026-05-11-advice-management/builds/build.json
diff --git a/openspec/changes/advice-management/context-brief.md b/openspec/changes/archive/2026-05-11-advice-management/context-brief.md
similarity index 100%
rename from openspec/changes/advice-management/context-brief.md
rename to openspec/changes/archive/2026-05-11-advice-management/context-brief.md
diff --git a/openspec/changes/advice-management/design.md b/openspec/changes/archive/2026-05-11-advice-management/design.md
similarity index 100%
rename from openspec/changes/advice-management/design.md
rename to openspec/changes/archive/2026-05-11-advice-management/design.md
diff --git a/openspec/changes/advice-management/hydra.json b/openspec/changes/archive/2026-05-11-advice-management/hydra.json
similarity index 100%
rename from openspec/changes/advice-management/hydra.json
rename to openspec/changes/archive/2026-05-11-advice-management/hydra.json
diff --git a/openspec/changes/advice-management/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-advice-management/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/advice-management/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-advice-management/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/advice-management/pipeline-logs/build.2.jsonl.gz b/openspec/changes/archive/2026-05-11-advice-management/pipeline-logs/build.2.jsonl.gz
similarity index 100%
rename from openspec/changes/advice-management/pipeline-logs/build.2.jsonl.gz
rename to openspec/changes/archive/2026-05-11-advice-management/pipeline-logs/build.2.jsonl.gz
diff --git a/openspec/changes/advice-management/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-advice-management/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/advice-management/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-advice-management/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/advice-management/proposal.md b/openspec/changes/archive/2026-05-11-advice-management/proposal.md
similarity index 100%
rename from openspec/changes/advice-management/proposal.md
rename to openspec/changes/archive/2026-05-11-advice-management/proposal.md
diff --git a/openspec/changes/advice-management/specs/advice-management/spec.md b/openspec/changes/archive/2026-05-11-advice-management/specs/advice-management/spec.md
similarity index 92%
rename from openspec/changes/advice-management/specs/advice-management/spec.md
rename to openspec/changes/archive/2026-05-11-advice-management/specs/advice-management/spec.md
index b0f25a1e..450c2e6a 100644
--- a/openspec/changes/advice-management/specs/advice-management/spec.md
+++ b/openspec/changes/archive/2026-05-11-advice-management/specs/advice-management/spec.md
@@ -8,13 +8,12 @@ Enable structured advice request management (adviezen) within Procest case flows
Dutch municipal case handling (VTH, bezwaar, vergunningverlening) regularly requires formal advice from internal specialists (welstandscommissie, juridische dienst) or external authorities (Veiligheidsregio, RUD). Without structured tracking, these advice requests are managed ad-hoc in email, deadlines are missed, and cases advance prematurely. The `adviesAanvraag` entity (ADR-000) provides the data model; this spec defines the behaviour.
-## Requirements
-
-### REQ-ADV-001: Advice Request Schema
+## ADDED Requirements
+### Requirement: REQ-ADV-001 — Advice Request Schema
The system SHALL store advice requests as `adviesAanvraag` objects in OpenRegister, supporting the full intern/extern advice lifecycle with deadline tracking and document attachment.
-#### REQ-ADV-001-001: Create internal advice request
+#### Scenario: Create internal advice request
- **GIVEN** the behandelaar is viewing a case dashboard for case `zaak-0042`
- **AND** they click "Advies aanvragen" and select an internal adviseur (e.g., welstandscommissie)
@@ -30,7 +29,7 @@ The system SHALL store advice requests as `adviesAanvraag` objects in OpenRegist
- **AND** a task SHALL be created for the adviseur: "Advies uitbrengen voor [case identifier]"
- **AND** the case activity log SHALL record: "Advies aangevraagd bij [adviseur]"
-#### REQ-ADV-001-002: Create external advice request
+#### Scenario: Create external advice request
- **GIVEN** the behandelaar requests advice from an external party (e.g., Veiligheidsregio Amsterdam-Amstelland)
- **WHEN** they submit with `type` = `extern` and an organization name as adviseur
@@ -38,7 +37,7 @@ The system SHALL store advice requests as `adviesAanvraag` objects in OpenRegist
- **AND** a Nextcloud reminder notification SHALL be scheduled 3 days before the deadline
- **AND** if the deadline passes without a response, an escalation notification SHALL be sent to the behandelaar and teamleider
-#### REQ-ADV-001-003: Receive and process advice
+#### Scenario: Receive and process advice
- **GIVEN** the adviseur (or behandelaar on their behalf) uploads an advice document to the `adviesAanvraag` object
- **AND** marks the request as completed
@@ -49,7 +48,7 @@ The system SHALL store advice requests as `adviesAanvraag` objects in OpenRegist
- **AND** the behandelaar SHALL receive a Nextcloud notification: "Advies ontvangen voor [case identifier]"
- **AND** the case activity log SHALL record: "Advies ontvangen van [adviseur]"
-#### REQ-ADV-001-004: Advice deadline expiry
+#### Scenario: Advice deadline expiry
- **GIVEN** an `adviesAanvraag` with `status` = `aangevraagd` exists
- **WHEN** the `AdviceDeadlineJob` runs and the `deadline` date has passed
@@ -57,7 +56,7 @@ The system SHALL store advice requests as `adviesAanvraag` objects in OpenRegist
- **AND** a task SHALL be created for the behandelaar: "Advies verlopen: beoordeel of vergunningprocedure kan doorgaan zonder dit advies"
- **AND** an escalation notification SHALL be sent to the behandelaar and teamleider
-#### REQ-ADV-001-005: Deadline reminder notification
+#### Scenario: Deadline reminder notification
- **GIVEN** an `adviesAanvraag` with `status` = `aangevraagd` and a `deadline` that is 3 days away
- **WHEN** the `AdviceDeadlineJob` runs
@@ -65,11 +64,11 @@ The system SHALL store advice requests as `adviesAanvraag` objects in OpenRegist
---
-### REQ-ADV-002: Advice Panel on Case Dashboard
+### Requirement: REQ-ADV-002 — Advice Panel on Case Dashboard
The system SHALL display an "Adviezen" panel on the case detail view showing all advice requests with their status, type, and deadline information.
-#### REQ-ADV-002-001: Display advice overview
+#### Scenario: Display advice overview
- **GIVEN** a user views the case detail for a case with 3 `adviesAanvraag` records
- **WHEN** the "Adviezen" panel renders
@@ -81,7 +80,7 @@ The system SHALL display an "Adviezen" panel on the case detail view showing all
- **AND** requests with a deadline in the past and status `aangevraagd` or `verlopen` SHALL be highlighted in red
- **AND** the number of days overdue SHALL be shown next to overdue requests
-#### REQ-ADV-002-002: Quick actions on advice panel
+#### Scenario: Quick actions on advice panel
- **GIVEN** the behandelaar views the "Adviezen" panel
- **WHEN** they interact with an individual advice request row
@@ -91,7 +90,7 @@ The system SHALL display an "Adviezen" panel on the case detail view showing all
- `aangevraagd` with a document uploaded → "Markeer als ontvangen" (transitions to `ontvangen`)
- **AND** no quick actions SHALL be shown for requests with status `verlopen` except a link to the generated task
-#### REQ-ADV-002-003: Empty state
+#### Scenario: Empty state
- **GIVEN** a case has no `adviesAanvraag` records
- **WHEN** the "Adviezen" panel renders
@@ -100,11 +99,11 @@ The system SHALL display an "Adviezen" panel on the case detail view showing all
---
-### REQ-ADV-003: Advice Request Form
+### Requirement: REQ-ADV-003 — Advice Request Form
The system SHALL provide a form dialog for creating advice requests directly from the case detail view.
-#### REQ-ADV-003-001: Create advice request dialog
+#### Scenario: Create advice request dialog
- **GIVEN** the behandelaar clicks "Advies aanvragen" on the case dashboard
- **WHEN** the dialog opens
@@ -117,7 +116,7 @@ The system SHALL provide a form dialog for creating advice requests directly fro
- **AND** the form SHALL validate that `adviseur` and `deadline` are filled before enabling submission
- **AND** on submission, the `adviesAanvraag` SHALL be created and the panel SHALL refresh
-#### REQ-ADV-003-002: Advice guard on workflow transition
+#### Scenario: Advice guard on workflow transition
- **GIVEN** a workflow transition on a case is configured with a guard of type `adviesGuard`
- **WHEN** the behandelaar attempts to trigger the transition
@@ -126,7 +125,7 @@ The system SHALL provide a form dialog for creating advice requests directly fro
- **AND** the guard violation message SHALL list each pending advice: "[adviseur]: advies verwacht voor [deadline DD-MM-YYYY]"
- **AND** the transition button SHALL be disabled with a tooltip explaining the guard
-#### REQ-ADV-003-003: Form accessibility
+#### Scenario: Form accessibility
- **GIVEN** the behandelaar navigates the advice request dialog using only a keyboard
- **WHEN** they tab through the form fields
diff --git a/openspec/changes/advice-management/tasks.md b/openspec/changes/archive/2026-05-11-advice-management/tasks.md
similarity index 100%
rename from openspec/changes/advice-management/tasks.md
rename to openspec/changes/archive/2026-05-11-advice-management/tasks.md
diff --git a/openspec/changes/ai-assisted-processing/.openspec.yaml b/openspec/changes/archive/2026-05-11-ai-assisted-processing/.openspec.yaml
similarity index 100%
rename from openspec/changes/ai-assisted-processing/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-ai-assisted-processing/.openspec.yaml
diff --git a/openspec/changes/ai-assisted-processing/builds/build.json b/openspec/changes/archive/2026-05-11-ai-assisted-processing/builds/build.json
similarity index 100%
rename from openspec/changes/ai-assisted-processing/builds/build.json
rename to openspec/changes/archive/2026-05-11-ai-assisted-processing/builds/build.json
diff --git a/openspec/changes/ai-assisted-processing/design.md b/openspec/changes/archive/2026-05-11-ai-assisted-processing/design.md
similarity index 100%
rename from openspec/changes/ai-assisted-processing/design.md
rename to openspec/changes/archive/2026-05-11-ai-assisted-processing/design.md
diff --git a/openspec/changes/ai-assisted-processing/proposal.md b/openspec/changes/archive/2026-05-11-ai-assisted-processing/proposal.md
similarity index 100%
rename from openspec/changes/ai-assisted-processing/proposal.md
rename to openspec/changes/archive/2026-05-11-ai-assisted-processing/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-ai-assisted-processing/specs/ai-assisted-processing/spec.md b/openspec/changes/archive/2026-05-11-ai-assisted-processing/specs/ai-assisted-processing/spec.md
new file mode 100644
index 00000000..c708c8f2
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-ai-assisted-processing/specs/ai-assisted-processing/spec.md
@@ -0,0 +1,330 @@
+---
+status: implemented
+---
+# ai-assisted-processing Specification
+
+## Purpose
+Enable AI-assisted case processing in Procest using the existing MCP (Model Context Protocol) integration. AI capabilities include document classification and data extraction, knowledge base Q&A (RAG) for case worker support, decision support suggestions, case routing recommendations, and auto-summarization. AI assists human case workers rather than making autonomous decisions -- every AI suggestion requires human confirmation.
+
+## Context
+AI-assisted processing is an emerging capability in modern case management platforms. Flowable's Agentic AI integrates orchestrator, knowledge, document, and utility AI agents directly into the CMMN engine with full audit trails. Our MCP integration with n8n provides the foundation for similar capabilities without requiring a proprietary AI engine -- n8n workflows orchestrate AI model calls while Procest surfaces the results in the case worker UI. This spec defines how AI capabilities surface in Procest, following the human-in-the-loop principle mandated by Dutch government AI governance (Algoritmeregister).
+
+## ADDED Requirements
+### Requirement: Document classification with zaaktype and metadata suggestion
+When documents are uploaded to a case or arrive unclassified, AI MUST suggest classification with confidence scoring.
+
+#### Scenario: Classify incoming document by type
+- **GIVEN** a PDF document uploaded to case `zaak-1`
+- **WHEN** the case worker clicks "AI classificeren" on the document in the case detail view
+- **THEN** the system MUST send the document content to the configured AI model via an n8n workflow triggered through MCP
+- **AND** return a suggested `documentType` (from the case type's configured document types) with a confidence score (0.0-1.0)
+- **AND** return suggested metadata fields (date, sender, subject) extracted from the document content
+- **AND** the case worker MUST confirm or modify the suggestion before it is applied to the `caseDocument` record
+
+#### Scenario: Route unclassified document to correct case
+- **GIVEN** a document arrives via OpenConnector without case linkage
+- **WHEN** the case worker triggers "AI routeren" on the document
+- **THEN** the AI MUST analyze the document content and compare it against active cases in the register
+- **AND** return up to 5 candidate cases ranked by relevance score
+- **AND** each candidate MUST show the case title, identifier, zaaktype, and relevance explanation
+- **AND** the case worker MUST select the correct case to link the document
+
+#### Scenario: Auto-suggest classification on upload
+- **GIVEN** AI auto-classification is enabled in app settings
+- **WHEN** a document is uploaded to a case
+- **THEN** the system MUST automatically trigger classification in the background
+- **AND** display the suggestion as a dismissable banner on the document: "AI suggests: Bezwaarschrift (87% confidence)"
+- **AND** the suggestion MUST expire after 7 days if not acted upon
+
+#### Scenario: Classification model selection per zaaktype
+- **GIVEN** different zaaktypes may benefit from different classification prompts
+- **WHEN** an admin configures AI classification for a specific zaaktype
+- **THEN** they MUST be able to specify a custom system prompt that includes zaaktype-specific document type descriptions
+- **AND** the default prompt MUST use the document type names and descriptions from the zaaktype configuration
+
+#### Scenario: Classification handles non-text documents
+- **GIVEN** a scanned image document (TIFF/JPEG) is uploaded
+- **WHEN** the case worker triggers "AI classificeren"
+- **THEN** the system MUST first perform OCR (via Docudesk or the AI model's vision capabilities)
+- **AND** then classify the extracted text
+- **AND** indicate to the case worker that OCR was used with the OCR confidence level
+
+### Requirement: Data extraction from documents to case fields
+AI MUST read document content and suggest field values for the case or related objects.
+
+#### Scenario: Extract structured data from application document
+- **GIVEN** a permit application PDF attached to case `zaak-1` with zaaktype `omgevingsvergunning`
+- **WHEN** the case worker triggers "AI extractie"
+- **THEN** the system MUST extract key-value pairs from the document content
+- **AND** map them to the case's property definitions (e.g., `applicant_name`, `address`, `requested_activity`)
+- **AND** present the extracted values as pre-filled suggestions in the case form (editable, not auto-saved)
+- **AND** the case worker MUST review and confirm each extracted value before it is saved
+
+#### Scenario: Confidence indicators per extracted field
+- **GIVEN** AI extracts 10 fields from a document
+- **WHEN** presenting results to the case worker
+- **THEN** each field MUST show a confidence indicator: high (>0.85), medium (0.60-0.85), low (<0.60)
+- **AND** low-confidence fields MUST be visually highlighted with an orange border for careful review
+- **AND** the case worker MUST explicitly confirm low-confidence fields (not just bulk-accept)
+
+#### Scenario: Extraction from multiple documents
+- **GIVEN** a case with 5 uploaded documents
+- **WHEN** the case worker triggers "AI extractie" on the case level (not a single document)
+- **THEN** the AI MUST analyze all documents and merge extracted fields, preferring the highest-confidence value when conflicts occur
+- **AND** conflicting values MUST be flagged for manual resolution with source document references
+
+#### Scenario: Extraction template per zaaktype
+- **GIVEN** a zaaktype with specific property definitions
+- **WHEN** AI extraction runs
+- **THEN** the extraction prompt MUST include the zaaktype's property definitions as the target schema
+- **AND** only extract fields that match defined properties (no arbitrary key-value extraction)
+
+#### Scenario: Extraction preserves source reference
+- **GIVEN** an extracted field value "Jan de Vries" for property "applicant_name"
+- **THEN** the extraction result MUST include the source document name, page number, and surrounding text snippet
+- **AND** this reference MUST be viewable by the case worker when hovering over the extracted value
+
+### Requirement: Knowledge base Q&A (RAG) for case worker support
+RAG-based Q&A MUST allow case workers to ask questions about policies, procedures, and regulations relevant to their case.
+
+#### Scenario: Ask a policy question in case context
+- **GIVEN** a case worker handling an `omgevingsvergunning` case
+- **WHEN** they open the AI assistant panel and ask "Wat zijn de maximale bouwhoogtes in zone B?"
+- **THEN** the system MUST search relevant policy documents in the knowledge base via RAG
+- **AND** return an answer with source citations (document name, page/section, direct quote)
+- **AND** the answer MUST be scoped to the municipality's own policy documents first, then national regulations
+
+#### Scenario: No answer available -- refuse to hallucinate
+- **GIVEN** a case worker asks a question with no relevant documents in the knowledge base
+- **THEN** the system MUST respond with "Geen relevante informatie gevonden in de kennisbank"
+- **AND** suggest: "Voeg relevante beleidsdocumenten toe aan de kennisbank"
+- **AND** MUST NOT generate a plausible-sounding but unsourced answer
+
+#### Scenario: Knowledge base population from case documents
+- **GIVEN** an admin enables "auto-index case documents" for a zaaktype
+- **WHEN** documents are uploaded to cases of that type
+- **THEN** policy documents (beleidsstukken, verordeningen) MUST be automatically indexed in the RAG knowledge base
+- **AND** case-specific documents (citizen applications, personal data) MUST NOT be indexed unless explicitly marked as policy documents
+
+#### Scenario: Context-aware answers
+- **GIVEN** a case worker asks "Hoeveel tijd heb ik nog voor een besluit?"
+- **WHEN** the AI assistant has access to the current case's deadline information
+- **THEN** the answer MUST include the specific deadline date and days remaining from the case data
+- **AND** cite the relevant legal basis for the deadline (e.g., WOO Art. 4.4 for WOO cases)
+
+#### Scenario: Conversation history within case
+- **GIVEN** a case worker has asked 3 questions in the AI assistant for case `zaak-1`
+- **WHEN** they ask a follow-up question
+- **THEN** the system MUST include the previous questions and answers as conversation context
+- **AND** the conversation history MUST be stored on the case for audit and handover purposes
+
+### Requirement: Decision support and next-action suggestions
+AI MUST analyze case state and history to suggest what the case worker should do next.
+
+#### Scenario: Suggest next step based on case state
+- **GIVEN** case `zaak-1` has status `intake_complete` and all required documents are uploaded
+- **WHEN** the case worker opens the case
+- **THEN** the AI assistant panel MAY show: "Alle intake documenten zijn aanwezig. Overweeg de zaak naar beoordelingsfase te verplaatsen."
+- **AND** the suggestion MUST be dismissable and non-blocking
+- **AND** the suggestion MUST include a one-click action to execute the suggested step
+
+#### Scenario: Flag potential deadline issues
+- **GIVEN** case `zaak-1` has a bezwaartermijn ending in 3 days and no decision recorded
+- **WHEN** the case worker opens the case
+- **THEN** the AI MUST flag: "Bezwaartermijn verloopt over 3 dagen -- besluit is mogelijk nodig"
+- **AND** the flag MUST appear as a prominent warning in the case detail header
+- **AND** link to the relevant deadline information in the `DeadlinePanel`
+
+#### Scenario: Summarize case for handover
+- **GIVEN** a case worker requests "AI samenvatting" for case `zaak-1`
+- **WHEN** the AI processes the case data (status history, documents, notes, tasks)
+- **THEN** it MUST generate a structured summary with: current status, key dates, open tasks, recent activity, and recommended next steps
+- **AND** the summary MUST be savable as a case note in the `ActivityTimeline`
+
+#### Scenario: Similar case detection
+- **GIVEN** a new case is created with certain properties (zaaktype, subject, applicant)
+- **WHEN** the case worker triggers "Vergelijkbare zaken zoeken"
+- **THEN** the AI MUST search for similar completed cases based on content similarity
+- **AND** return up to 5 similar cases with their outcomes (resultaat) and processing time
+- **AND** the case worker MUST be able to view the similar cases for reference
+
+#### Scenario: Workload balancing suggestions
+- **GIVEN** a team has 50 active cases distributed across 5 case workers
+- **WHEN** a manager views the team dashboard
+- **THEN** the AI MAY suggest workload redistribution: "Medewerker A heeft 15 zaken (3 urgent), medewerker B heeft 5. Overweeg herverdeling."
+- **AND** the suggestion MUST be based on case count, urgency, and estimated complexity
+
+### Requirement: Case auto-summarization
+AI MUST generate human-readable summaries of case content for quick orientation.
+
+#### Scenario: Auto-summary on case open
+- **GIVEN** a case with more than 5 documents and 10 timeline entries
+- **WHEN** the case worker opens the case for the first time (or after 7+ days)
+- **THEN** the system MAY display an auto-generated summary panel at the top of the case detail
+- **AND** the summary MUST cover: what the case is about, current status, key dates, and what needs attention
+
+#### Scenario: Document summary
+- **GIVEN** a 25-page policy document attached to a case
+- **WHEN** the case worker clicks "AI samenvatting" on the document
+- **THEN** the system MUST generate a 3-5 sentence summary of the document
+- **AND** display it inline below the document title in the case document list
+
+#### Scenario: Timeline summary for long-running cases
+- **GIVEN** a case with 50+ timeline entries spanning 6 months
+- **WHEN** the case worker clicks "Tijdlijn samenvatting"
+- **THEN** the AI MUST generate a chronological summary highlighting key events (status changes, decisions, escalations)
+- **AND** the summary MUST be displayable as a collapsed panel above the full timeline
+
+### Requirement: AI interaction audit trail
+Every AI suggestion, acceptance, and rejection MUST be recorded for accountability and Algoritmeregister compliance.
+
+#### Scenario: Audit trail for accepted suggestion
+- **GIVEN** AI suggests `documentType: "bezwaarschrift"` for a document with confidence 0.92
+- **WHEN** the case worker accepts the suggestion
+- **THEN** an audit entry MUST be created in the case's activity log with:
+ - `type`: `ai.suggestion.accepted`
+ - `model`: the AI model identifier (e.g., "ollama/llama3.1")
+ - `suggestion`: the original suggestion payload
+ - `confidence`: 0.92
+ - `user`: the case worker who accepted
+ - `timestamp`: ISO 8601 datetime
+
+#### Scenario: Audit trail for rejected suggestion
+- **GIVEN** AI suggests routing a document to case `zaak-1`
+- **WHEN** the case worker rejects the suggestion and manually assigns to `zaak-2`
+- **THEN** an audit entry MUST record:
+ - `type`: `ai.suggestion.rejected`
+ - `suggestion`: `{"case": "zaak-1", "confidence": 0.78}`
+ - `actual`: `{"case": "zaak-2"}`
+ - `reason`: optional free-text reason from the case worker
+ - `user`: the case worker
+
+#### Scenario: Audit trail for RAG Q&A
+- **GIVEN** a case worker asks a question via the knowledge base
+- **THEN** an audit entry MUST record the question, the answer, the source documents cited, and the model used
+- **AND** this MUST be queryable for Algoritmeregister reporting
+
+#### Scenario: Aggregate AI usage reporting
+- **GIVEN** an admin requests AI usage statistics
+- **THEN** the system MUST provide: total suggestions made, acceptance rate, rejection rate, average confidence scores, most common suggestion types, and per-model usage breakdown
+
+#### Scenario: Audit entries are immutable
+- **GIVEN** an AI audit trail entry has been created
+- **THEN** it MUST NOT be editable or deletable by any user
+- **AND** it MUST be retained for at least the case's archival retention period
+
+### Requirement: AI case routing recommendations
+AI MUST suggest the best case worker or team for incoming cases based on expertise and workload.
+
+#### Scenario: Route new case to specialist
+- **GIVEN** a new WOO case arrives via intake
+- **WHEN** the case is created and AI routing is enabled
+- **THEN** the AI MUST analyze the case subject and recommend a case worker with WOO expertise
+- **AND** the recommendation MUST factor in current workload (number of active cases per worker)
+- **AND** the case worker MUST confirm assignment
+
+#### Scenario: Route based on geographic area
+- **GIVEN** a case related to a specific neighborhood or address
+- **WHEN** AI routing analyzes the case
+- **THEN** it MUST consider geographic assignment rules (wijkteam, gebiedsteam) if configured
+- **AND** suggest the case worker responsible for that area
+
+#### Scenario: Escalation routing
+- **GIVEN** a case that has been stalled for more than its expected processing time
+- **WHEN** the AI detects the stall during periodic analysis
+- **THEN** it MUST suggest escalation to a senior case worker or manager
+- **AND** include the stall duration and potential reasons in the suggestion
+
+### Requirement: AI features opt-in and configuration
+AI features MUST be individually toggleable per municipality, with support for local and cloud AI models.
+
+#### Scenario: Disable all AI features
+- **GIVEN** an admin navigates to Procest app settings
+- **WHEN** they toggle "AI-ondersteuning" to disabled
+- **THEN** no AI buttons, panels, or suggestions MUST appear in the case worker UI
+- **AND** no case data MUST be sent to any AI model
+- **AND** the toggle MUST take effect immediately without requiring app restart
+
+#### Scenario: Configure local AI model (Ollama)
+- **GIVEN** AI features are enabled
+- **WHEN** an admin configures the AI model as a local Ollama instance (e.g., `http://ollama:11434`)
+- **THEN** all AI requests MUST be routed to the local model
+- **AND** the admin MUST be able to select the specific model (e.g., llama3.1, mistral, qwen2.5)
+- **AND** document content MUST NOT leave the Nextcloud server network
+
+#### Scenario: Configure cloud AI model
+- **GIVEN** AI features are enabled
+- **WHEN** an admin configures an external AI model (OpenAI, Azure OpenAI, Anthropic)
+- **THEN** the system MUST display a warning: "Zaakgegevens worden naar een externe dienst verzonden. Zorg dat dit past binnen uw verwerkingsovereenkomst."
+- **AND** the admin MUST explicitly acknowledge the privacy implications
+- **AND** the configuration MUST store the API key securely via Nextcloud's credential store
+
+#### Scenario: Feature-level toggles
+- **GIVEN** AI features are globally enabled
+- **THEN** the admin MUST be able to individually toggle:
+ - Document classification (on/off)
+ - Data extraction (on/off)
+ - Knowledge base Q&A (on/off)
+ - Decision support suggestions (on/off)
+ - Auto-summarization (on/off)
+ - Case routing (on/off)
+- **AND** each feature MUST work independently
+
+#### Scenario: AI model health monitoring
+- **GIVEN** an AI model is configured
+- **THEN** the settings page MUST show the model connection status (connected/error)
+- **AND** a "Test verbinding" button MUST send a test prompt and display the response time
+- **AND** if the model is unreachable, AI features MUST gracefully degrade (hide AI buttons, show "AI niet beschikbaar" on hover)
+
+### Requirement: Privacy and data protection for AI processing
+AI processing MUST comply with AVG/GDPR and BIO requirements for government data.
+
+#### Scenario: Data minimization in AI prompts
+- **GIVEN** the system sends case data to an AI model for classification
+- **THEN** only the minimum necessary data MUST be included in the prompt (document content, not full case history)
+- **AND** BSN, financial data, and health information MUST be stripped from prompts unless explicitly required for the task
+
+#### Scenario: DPIA requirement tracking
+- **GIVEN** AI features are enabled for the first time
+- **THEN** the system MUST display a warning: "AI-verwerking van zaakgegevens vereist een Data Protection Impact Assessment (DPIA)"
+- **AND** the admin MUST acknowledge this requirement
+- **AND** the acknowledgement MUST be logged
+
+#### Scenario: Data retention for AI interactions
+- **GIVEN** AI interaction data (prompts, responses) is stored for audit purposes
+- **THEN** the retention period MUST match the case's archival retention period
+- **AND** when a case is destroyed per retention policy, associated AI audit data MUST also be destroyed
+
+## Dependencies
+- n8n MCP server (for AI workflow orchestration)
+- OpenRegister MCP (for case data access)
+- Ollama or external LLM provider (for AI model inference)
+- Docudesk (for OCR of scanned documents)
+- OpenConnector (for document ingestion from external sources)
+- Nextcloud AI integration (`OCP\TextProcessing`) as potential alternative backend
+
+---
+
+### Current Implementation Status
+
+**Not yet implemented.** No AI-related services, controllers, or Vue components exist in the Procest codebase. The MCP integration infrastructure exists at the workspace level (`.mcp.json` with n8n-mcp and OpenRegister MCP), but Procest itself has no AI document classification, data extraction, knowledge base Q&A, or decision support functionality.
+
+**Foundation available:**
+- The n8n MCP server is configured at the workspace level, providing workflow orchestration that could trigger AI pipelines.
+- OpenRegister MCP provides data access that AI tools could query.
+- The `objectStore` pattern (`src/store/modules/object.js`) with `auditTrailsPlugin` provides the audit infrastructure that AI interaction logging would use.
+- `ActivityTimeline.vue` supports activity entries with type, description, user, and date -- extensible for AI audit entries.
+- Nextcloud's `OCP\TextProcessing\IManager` provides a native AI abstraction that could serve as an alternative to direct MCP calls.
+
+**Partial implementations:** None.
+
+### Standards & References
+
+- **MCP (Model Context Protocol)**: Anthropic's standard for LLM tool integration -- the foundation for AI features.
+- **GDPR / AVG**: AI processing of citizen data requires Data Protection Impact Assessment (DPIA), especially for document classification containing PII.
+- **BIO (Baseline Informatiebeveiliging Overheid)**: Government security baseline applies to AI model endpoints and data handling.
+- **Algoritmeregister**: Dutch government requirement to register algorithmic decision-making systems. All AI features that influence case outcomes must be registered.
+- **Common Ground**: AI services should be deployable as Common Ground components (API-first, layered architecture).
+- **WCAG AA**: AI suggestion UI must be accessible, including screen reader announcements for suggestions.
+- **Flowable Agentic AI**: Reference architecture for integrating AI agents into CMMN case management (orchestrator, knowledge, document, utility agents).
+- **CMMN 1.1**: AI suggestions map to SentryEvents that can trigger case plan items.
diff --git a/openspec/changes/ai-assisted-processing/tasks.md b/openspec/changes/archive/2026-05-11-ai-assisted-processing/tasks.md
similarity index 100%
rename from openspec/changes/ai-assisted-processing/tasks.md
rename to openspec/changes/archive/2026-05-11-ai-assisted-processing/tasks.md
diff --git a/openspec/changes/appointment-scheduling/.openspec.yaml b/openspec/changes/archive/2026-05-11-appointment-scheduling/.openspec.yaml
similarity index 100%
rename from openspec/changes/appointment-scheduling/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-appointment-scheduling/.openspec.yaml
diff --git a/openspec/changes/appointment-scheduling/builds/build.json b/openspec/changes/archive/2026-05-11-appointment-scheduling/builds/build.json
similarity index 100%
rename from openspec/changes/appointment-scheduling/builds/build.json
rename to openspec/changes/archive/2026-05-11-appointment-scheduling/builds/build.json
diff --git a/openspec/changes/appointment-scheduling/design.md b/openspec/changes/archive/2026-05-11-appointment-scheduling/design.md
similarity index 100%
rename from openspec/changes/appointment-scheduling/design.md
rename to openspec/changes/archive/2026-05-11-appointment-scheduling/design.md
diff --git a/openspec/changes/appointment-scheduling/proposal.md b/openspec/changes/archive/2026-05-11-appointment-scheduling/proposal.md
similarity index 100%
rename from openspec/changes/appointment-scheduling/proposal.md
rename to openspec/changes/archive/2026-05-11-appointment-scheduling/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-appointment-scheduling/specs/appointment-scheduling/spec.md b/openspec/changes/archive/2026-05-11-appointment-scheduling/specs/appointment-scheduling/spec.md
new file mode 100644
index 00000000..91518565
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-appointment-scheduling/specs/appointment-scheduling/spec.md
@@ -0,0 +1,342 @@
+---
+status: implemented
+---
+# appointment-scheduling Specification
+
+## Purpose
+Integrate appointment scheduling (afsprakenbeheer) into Procest case flows for cases that require physical service delivery at a municipal counter (balie). Citizens can book appointments as part of case submission or at any point during case handling. The system integrates with existing municipal appointment backends (Qmatic, JCC Afspraken) via a plugin architecture, and supports self-service cancellation and modification.
+
+## Context
+In Dutch municipalities, balie appointments are standard for services like passport collection, marriage registration, and permit discussions. Open-Formulieren implements appointment scheduling as part of form submissions with integration plugins for JCC and Qmatic -- product/location/timeslot selection during intake with configurable contact details. This is the reference model. Procest extends this by embedding appointments into the case lifecycle, making appointment status visible in case context, and supporting both citizen self-service and case worker-initiated scheduling.
+
+## ADDED Requirements
+### Requirement: Appointments bookable as part of case flow
+Case workers or citizens MUST be able to create appointments linked to a case at any point during the case lifecycle.
+
+#### Scenario: Book appointment during case intake
+- **GIVEN** a citizen is submitting a `paspoort_aanvraag` case
+- **AND** the zaaktype is configured with `requiresAppointment: true`
+- **WHEN** the citizen reaches the appointment step in the intake flow
+- **THEN** the system MUST show:
+ - Available products (e.g., "Paspoort ophalen", "Rijbewijs ophalen") filtered by zaaktype configuration
+ - Available locations (e.g., "Stadskantoor", "Wijkkantoor Noord") for the selected product
+ - Available dates and timeslots for the selected product/location combination
+- **AND** the citizen MUST select a timeslot to proceed with case submission
+- **AND** the appointment MUST be automatically linked to the created case
+
+#### Scenario: Book appointment from case detail view
+- **GIVEN** case `zaak-1` is in progress and needs a physical meeting
+- **WHEN** a case worker clicks "Plan afspraak" in the `CaseDetail.vue` header actions
+- **THEN** an appointment booking dialog MUST appear with:
+ - Product pre-selected based on the zaaktype (editable)
+ - Location dropdown with configured municipal locations
+ - Date picker showing available dates
+ - Timeslot grid for the selected date
+- **AND** the appointment MUST be linked to `zaak-1` after booking
+- **AND** an activity entry MUST appear in the `ActivityTimeline`
+
+#### Scenario: Multiple appointments per case
+- **GIVEN** case `zaak-1` already has an appointment for document submission
+- **WHEN** the case worker books a second appointment for document collection
+- **THEN** both appointments MUST be listed in the case's appointment section
+- **AND** each appointment MUST have its own status and lifecycle
+
+#### Scenario: Appointment as required task
+- **GIVEN** a zaaktype configured with an appointment required at status "Ophalen"
+- **WHEN** the case reaches the "Ophalen" status
+- **THEN** a task MUST be auto-created: "Plan afspraak voor ophalen"
+- **AND** the case MUST NOT be advanceable to the next status until the appointment is booked
+
+#### Scenario: Appointment links to case participants
+- **GIVEN** a case with a linked citizen (role: initiator, with BSN and contact details)
+- **WHEN** booking an appointment
+- **THEN** the citizen's name, phone number, and email MUST be pre-filled from the case role data
+- **AND** the case worker MUST be able to override the contact details (e.g., if someone else will attend)
+
+### Requirement: Pluggable appointment backend architecture
+Different municipalities use different appointment systems; the integration MUST be pluggable.
+
+#### Scenario: JCC Afspraken integration
+- **GIVEN** the municipality uses JCC Afspraken
+- **AND** the JCC plugin is configured in Procest settings with: API URL, API key, and organization ID
+- **WHEN** a timeslot query is made for product "Paspoort ophalen" at location "Stadskantoor"
+- **THEN** the plugin MUST call the JCC API endpoint `/openapi/v1/beschikbaarheid` to retrieve available slots
+- **AND** booking MUST call JCC's `/openapi/v1/afspraken` to create the appointment
+- **AND** the JCC appointment ID MUST be stored on the Procest appointment record for sync
+- **AND** cancellation MUST call JCC's delete endpoint to cancel in both systems
+
+#### Scenario: Qmatic Orchestra integration
+- **GIVEN** the municipality uses Qmatic Orchestra
+- **AND** the Qmatic plugin is configured with: base URL, API key, and branch ID
+- **WHEN** a timeslot query is made
+- **THEN** the plugin MUST call the Qmatic REST API (`/rest/servicepoint/branches/{id}/dates/{date}/times`)
+- **AND** booking MUST create the appointment in Qmatic
+- **AND** the Qmatic appointment reference MUST be stored on the Procest record
+
+#### Scenario: Fallback manual scheduling (no backend)
+- **GIVEN** no appointment backend is configured
+- **WHEN** a case worker creates an appointment
+- **THEN** the appointment MUST be stored locally in OpenRegister as an appointment object
+- **AND** a Nextcloud Calendar event MUST be created via `OCP\Calendar\IManager`
+- **AND** the calendar event MUST include the case reference, citizen name, product, and location
+
+#### Scenario: Plugin registration via OpenConnector
+- **GIVEN** the plugin architecture uses OpenConnector as the API adapter layer
+- **WHEN** an admin configures a new appointment backend
+- **THEN** they MUST select the backend type (JCC/Qmatic/Custom) and configure the connection via OpenConnector source settings
+- **AND** the system MUST validate the connection with a test call before saving
+
+#### Scenario: Backend failover handling
+- **GIVEN** the JCC API returns a 503 Service Unavailable error
+- **WHEN** a case worker attempts to book an appointment
+- **THEN** the system MUST display: "Afsprakensysteem tijdelijk niet beschikbaar. Probeer het later opnieuw."
+- **AND** the error MUST be logged with timestamp and response details
+- **AND** the system MUST NOT fall back to manual scheduling unless explicitly configured
+
+### Requirement: Citizen self-service appointment management
+Citizens MUST be able to cancel, reschedule, and view their appointments without contacting the municipality.
+
+#### Scenario: Cancel an appointment via confirmation link
+- **GIVEN** citizen has appointment `apt-1` for March 25, 2026 at 10:00 at Stadskantoor
+- **AND** the citizen received a confirmation email with a unique cancellation link
+- **WHEN** the citizen opens the link and clicks "Annuleren"
+- **THEN** a confirmation dialog MUST appear: "Weet u zeker dat u uw afspraak op 25 maart om 10:00 wilt annuleren?"
+- **AND** upon confirmation, the appointment MUST be cancelled in both Procest and the backend system (JCC/Qmatic)
+- **AND** a cancellation confirmation MUST be sent (email and/or SMS based on configuration)
+- **AND** the case `ActivityTimeline` MUST record: "Afspraak geannuleerd door burger"
+
+#### Scenario: Reschedule an appointment
+- **GIVEN** citizen has appointment `apt-1` for March 25 at 10:00
+- **WHEN** the citizen accesses their appointment via the confirmation link and clicks "Verzetten"
+- **THEN** available alternative timeslots MUST be shown for the same product and location
+- **AND** selecting a new slot MUST atomically cancel the old appointment and book the new one
+- **AND** a new confirmation MUST be sent with updated date/time/location
+
+#### Scenario: View appointment details
+- **GIVEN** a citizen accesses their appointment link
+- **THEN** the page MUST show: date, time, location (with address and map link), product, what to bring, and the case reference number
+- **AND** provide buttons for "Annuleren" and "Verzetten"
+- **AND** the page MUST NOT require authentication (token-based access)
+
+#### Scenario: Cancellation deadline enforcement
+- **GIVEN** the municipality configures a minimum cancellation notice of 24 hours
+- **WHEN** a citizen attempts to cancel appointment `apt-1` that starts in 4 hours
+- **THEN** the system MUST display: "Annuleren is niet meer mogelijk. Neem contact op met de gemeente."
+- **AND** provide a phone number or contact form link
+
+#### Scenario: Self-service link expiration
+- **GIVEN** appointment `apt-1` was scheduled for March 25 at 10:00
+- **AND** today is March 26 (appointment has passed)
+- **WHEN** the citizen accesses the confirmation link
+- **THEN** the page MUST show: "Deze afspraak heeft plaatsgevonden op 25 maart 2026"
+- **AND** cancellation and rescheduling MUST be disabled
+
+### Requirement: Appointment lifecycle and reminder notifications
+Appointments MUST track status through their lifecycle with automated reminders to reduce no-shows.
+
+#### Scenario: Appointment confirmation notification
+- **GIVEN** a citizen books appointment `apt-1` for March 25 at 10:00 at Stadskantoor
+- **THEN** a confirmation MUST be sent (configurable: email, SMS, or both) containing:
+ - Date, time, and location with address
+ - Product name (what the appointment is for)
+ - What to bring (linked to zaaktype `requiresDocuments` configuration)
+ - Cancellation/modification link (unique token-based URL)
+ - Case reference number
+- **AND** the confirmation MUST be sent via an n8n workflow for template flexibility
+
+#### Scenario: Reminder notification before appointment
+- **GIVEN** appointment `apt-1` is scheduled for tomorrow at 10:00
+- **WHEN** the Nextcloud cron job runs the reminder check
+- **THEN** a reminder MUST be sent to the citizen via the configured channel
+- **AND** the reminder interval MUST be configurable per zaaktype (default: 1 day before)
+- **AND** the reminder MUST include a "not able to make it" link for easy cancellation
+
+#### Scenario: No-show recording
+- **GIVEN** appointment `apt-1` was scheduled for 10:00 and the citizen did not appear
+- **WHEN** the case worker marks the appointment as "Niet verschenen" (no-show)
+- **THEN** the appointment status MUST change to `niet_verschenen`
+- **AND** the case `ActivityTimeline` MUST record: "Burger niet verschenen bij afspraak"
+- **AND** a follow-up task MUST be auto-created: "Contact opnemen na niet-verschijnen" if configured
+
+#### Scenario: Appointment completed
+- **GIVEN** appointment `apt-1` took place
+- **WHEN** the case worker marks it as "Afgerond" (completed)
+- **THEN** the appointment status MUST change to `afgerond`
+- **AND** the case timeline MUST record: "Afspraak gehouden: 25 maart 2026, 10:00, Stadskantoor"
+- **AND** if the zaaktype has a post-appointment status transition configured, the case MUST auto-advance
+
+#### Scenario: Appointment status lifecycle
+- **GIVEN** an appointment object in OpenRegister
+- **THEN** it MUST support the following statuses:
+ - `gepland` (initial, after booking)
+ - `herinnerd` (after reminder sent)
+ - `afgerond` (completed successfully)
+ - `niet_verschenen` (no-show)
+ - `geannuleerd` (cancelled by citizen or case worker)
+ - `verzet` (rescheduled -- old appointment gets this status)
+
+### Requirement: Appointment visibility in case context
+Appointment data MUST be visible in the case timeline and case detail view.
+
+#### Scenario: Appointment section in case detail
+- **GIVEN** case `zaak-1` has one or more appointments
+- **THEN** the case detail view MUST show an "Afspraken" section listing all appointments
+- **AND** each appointment MUST show: date/time, location, product, status, and citizen name
+- **AND** appointments MUST be ordered by date (upcoming first)
+
+#### Scenario: Timeline integration
+- **GIVEN** case `zaak-1` has an appointment lifecycle
+- **WHEN** viewing the `ActivityTimeline` component
+- **THEN** the following events MUST appear chronologically:
+ - "Afspraak gepland: 25 maart 2026, 10:00, Stadskantoor"
+ - "Herinnering verzonden naar burger"
+ - "Afspraak gehouden" or "Burger niet verschenen"
+- **AND** each event MUST include an icon appropriate to its type
+
+#### Scenario: Appointment in case list overview
+- **GIVEN** the case list view at `CaseList.vue`
+- **THEN** cases with upcoming appointments MUST show a calendar icon with the next appointment date
+- **AND** cases where the citizen was a no-show MUST show a warning indicator
+
+#### Scenario: Appointment on dashboard
+- **GIVEN** the Procest dashboard (`Dashboard.vue`)
+- **THEN** a "Komende afspraken" widget MUST list today's and tomorrow's appointments across all cases assigned to the current user
+- **AND** each entry MUST link to the case detail
+
+### Requirement: Real-time timeslot availability
+Shown timeslots MUST reflect current availability to prevent double bookings and stale data.
+
+#### Scenario: Live availability query
+- **GIVEN** a citizen or case worker is browsing available timeslots
+- **WHEN** they select a date
+- **THEN** the system MUST query the appointment backend in real-time (not cached) for that date
+- **AND** display available slots with capacity indicators (if the backend provides capacity data)
+
+#### Scenario: Concurrent booking prevention
+- **GIVEN** two citizens view the same timeslot as available
+- **WHEN** both attempt to book it simultaneously
+- **THEN** only one booking MUST succeed (the backend system handles atomicity)
+- **AND** the other MUST receive: "Dit tijdslot is zojuist geboekt. Kies een ander tijdslot."
+- **AND** the timeslot grid MUST refresh to show updated availability
+
+#### Scenario: Timeslot expiration during booking
+- **GIVEN** a citizen has been on the booking page for 15 minutes without completing
+- **THEN** the system MUST display: "Beschikbaarheid kan gewijzigd zijn. Vernieuw de tijdsloten."
+- **AND** provide a refresh button to reload current availability
+
+#### Scenario: Availability filtered by capacity
+- **GIVEN** a location has 3 service desks (balies) available
+- **AND** 2 are already booked for the 10:00-10:15 slot
+- **THEN** the slot MUST still show as available (1 remaining)
+- **AND** when all 3 are booked, the slot MUST show as unavailable
+
+### Requirement: Product and location configuration
+Administrators MUST be able to configure which products and locations are available for appointment booking.
+
+#### Scenario: Configure products per zaaktype
+- **GIVEN** the admin is editing a zaaktype in `CaseTypeDetail.vue`
+- **THEN** a "Products" tab MUST allow adding appointment products
+- **AND** each product MUST have: name, description, estimated duration (minutes), and backend product ID (for JCC/Qmatic mapping)
+- **AND** products MUST be linkable to specific zaaktype statuses (e.g., "Paspoort ophalen" only available at status "Ophalen")
+
+#### Scenario: Configure locations
+- **GIVEN** the admin navigates to appointment settings
+- **THEN** they MUST be able to manage locations with: name, address, phone number, opening hours, and backend location ID
+- **AND** locations MUST be filterable by which products they offer
+
+#### Scenario: Location-specific availability rules
+- **GIVEN** location "Wijkkantoor Noord" is only open Tuesday through Thursday
+- **WHEN** a citizen selects this location
+- **THEN** only Tuesday, Wednesday, and Thursday dates MUST be shown in the date picker
+- **AND** the opening hours MUST be configured per location in the admin settings
+
+#### Scenario: Seasonal closures and holidays
+- **GIVEN** the municipality configures holidays and closure dates
+- **THEN** those dates MUST be excluded from appointment availability
+- **AND** existing appointments on newly added closure dates MUST be flagged for rescheduling
+
+### Requirement: Appointment data model in OpenRegister
+Appointments MUST be stored as OpenRegister objects with a defined schema.
+
+#### Scenario: Appointment schema definition
+- **GIVEN** the Procest register configuration
+- **THEN** an `appointment` schema MUST be defined with fields:
+ - `id` (UUID, auto-generated)
+ - `caseId` (reference to case)
+ - `citizenName` (string)
+ - `citizenEmail` (string)
+ - `citizenPhone` (string)
+ - `product` (string, from configured products)
+ - `location` (string, from configured locations)
+ - `dateTime` (ISO 8601 datetime)
+ - `duration` (integer, minutes)
+ - `status` (enum: gepland/herinnerd/afgerond/niet_verschenen/geannuleerd/verzet)
+ - `externalId` (string, JCC/Qmatic reference)
+ - `selfServiceToken` (string, unique token for citizen access)
+ - `notes` (text, case worker notes)
+ - `bookedBy` (string, user who created the booking)
+
+#### Scenario: Appointment linked to case via caseObject
+- **GIVEN** an appointment is created for case `zaak-1`
+- **THEN** a `caseObject` record MUST link the appointment to the case
+- **AND** querying the case's objects MUST include the appointment
+
+#### Scenario: Appointment history preserved
+- **GIVEN** appointment `apt-1` is rescheduled from March 25 to March 28
+- **THEN** the original appointment MUST be preserved with status `verzet`
+- **AND** a new appointment MUST be created with the new date and status `gepland`
+- **AND** both MUST be linked to the same case
+
+### Requirement: Notification channel configuration
+Appointment notifications MUST support multiple channels with per-municipality configuration.
+
+#### Scenario: Email notifications via n8n
+- **GIVEN** the municipality has email notifications configured
+- **WHEN** an appointment is booked
+- **THEN** the confirmation email MUST be sent via an n8n workflow
+- **AND** the email template MUST be customizable by the municipality (HTML template in n8n)
+
+#### Scenario: SMS notifications
+- **GIVEN** the municipality has SMS notifications enabled (via a configured SMS gateway in OpenConnector)
+- **WHEN** an appointment reminder is triggered
+- **THEN** an SMS MUST be sent with a short message: "Herinnering: uw afspraak morgen om 10:00 bij Stadskantoor. Niet kunnen komen? [link]"
+
+#### Scenario: Notification preferences per citizen
+- **GIVEN** a citizen has specified their notification preference during booking (email, SMS, or both)
+- **THEN** notifications MUST only be sent via the selected channel(s)
+- **AND** the preference MUST be stored on the appointment record
+
+## Dependencies
+- OpenRegister (for appointment data storage)
+- OpenConnector (for JCC/Qmatic API adapters and SMS gateway)
+- Nextcloud Calendar (`OCP\Calendar\IManager`) for fallback calendar events
+- n8n (for notification workflow orchestration)
+- Pipelinq (sister app -- appointments booked during CRM interactions may be linked to cases)
+- Mijn Overheid integration (appointment status as case status update)
+
+---
+
+### Current Implementation Status
+
+**Not yet implemented.** No appointment-related schemas, controllers, services, or Vue components exist in the Procest codebase. The `procest_register.json` configuration does not include an appointment schema.
+
+**Foundation available:**
+- Case detail view (`src/views/cases/CaseDetail.vue`) provides the integration point where a "Plan afspraak" button would be added in the header actions.
+- Activity timeline component (`src/views/cases/components/ActivityTimeline.vue`) could display appointment events.
+- `DeadlinePanel.vue` shows that date-based tracking UI patterns are established.
+- OpenConnector (external dependency) could host JCC/Qmatic API adapters.
+- The task management infrastructure (`src/views/tasks/`) could model appointment scheduling as a task type.
+- `NotificatieService.php` provides notification infrastructure.
+- n8n MCP tools can orchestrate notification workflows.
+
+**Partial implementations:** None.
+
+### Standards & References
+
+- **VNG GEMMA Referentiearchitectuur**: Afsprakenbeheer is a recognized component in the GEMMA zaakgericht werken reference architecture.
+- **JCC Afspraken API**: Proprietary API for municipal appointment scheduling (widely used in Dutch municipalities). OpenAPI v1 specification.
+- **Qmatic Orchestra REST API**: Standard integration for queue management and appointment booking.
+- **Open-Formulieren Appointment Plugin Architecture**: Reference implementation for pluggable appointment backends (JCC, Qmatic) with product/location/timeslot selection model.
+- **WCAG AA**: Appointment booking UI must be accessible, including date/time pickers that work with keyboard and screen readers.
+- **BRP (Basisregistratie Personen)**: Citizen identification for appointment linking via BSN.
+- **Nextcloud Calendar IManager**: OCP interface for creating calendar events as fallback appointment tracking.
diff --git a/openspec/changes/appointment-scheduling/tasks.md b/openspec/changes/archive/2026-05-11-appointment-scheduling/tasks.md
similarity index 100%
rename from openspec/changes/appointment-scheduling/tasks.md
rename to openspec/changes/archive/2026-05-11-appointment-scheduling/tasks.md
diff --git a/openspec/changes/base-register-seed-data/.openspec.yaml b/openspec/changes/archive/2026-05-11-base-register-seed-data/.openspec.yaml
similarity index 100%
rename from openspec/changes/base-register-seed-data/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-base-register-seed-data/.openspec.yaml
diff --git a/openspec/changes/base-register-seed-data/builds/build.json b/openspec/changes/archive/2026-05-11-base-register-seed-data/builds/build.json
similarity index 100%
rename from openspec/changes/base-register-seed-data/builds/build.json
rename to openspec/changes/archive/2026-05-11-base-register-seed-data/builds/build.json
diff --git a/openspec/changes/base-register-seed-data/delta-spec.md b/openspec/changes/archive/2026-05-11-base-register-seed-data/delta-spec.md
similarity index 100%
rename from openspec/changes/base-register-seed-data/delta-spec.md
rename to openspec/changes/archive/2026-05-11-base-register-seed-data/delta-spec.md
diff --git a/openspec/changes/base-register-seed-data/design.md b/openspec/changes/archive/2026-05-11-base-register-seed-data/design.md
similarity index 100%
rename from openspec/changes/base-register-seed-data/design.md
rename to openspec/changes/archive/2026-05-11-base-register-seed-data/design.md
diff --git a/openspec/changes/base-register-seed-data/proposal.md b/openspec/changes/archive/2026-05-11-base-register-seed-data/proposal.md
similarity index 100%
rename from openspec/changes/base-register-seed-data/proposal.md
rename to openspec/changes/archive/2026-05-11-base-register-seed-data/proposal.md
diff --git a/openspec/changes/base-register-seed-data/specs/base-register-seed-data/spec.md b/openspec/changes/archive/2026-05-11-base-register-seed-data/specs/base-register-seed-data/spec.md
similarity index 73%
rename from openspec/changes/base-register-seed-data/specs/base-register-seed-data/spec.md
rename to openspec/changes/archive/2026-05-11-base-register-seed-data/specs/base-register-seed-data/spec.md
index 083183d7..a3e2c138 100644
--- a/openspec/changes/base-register-seed-data/specs/base-register-seed-data/spec.md
+++ b/openspec/changes/archive/2026-05-11-base-register-seed-data/specs/base-register-seed-data/spec.md
@@ -37,15 +37,13 @@ Each file follows the OpenRegister JSON format: OpenAPI 3.0 envelope with `x-ope
---
-## Requirements
-
-### REQ-SEED-001: BRP Register (Basisregistratie Personen)
+## ADDED Requirements
+### Requirement: REQ-SEED-001 — BRP Register (Basisregistratie Personen)
The system MUST provide a `brp_register.json` file containing a BRP register with an `ingeschrevenPersoon` schema and at least 25 fictional person records.
**Feature tier**: MVP
-
##### Register Definition
| Field | Value |
@@ -92,36 +90,36 @@ The system MUST provide a `brp_register.json` file containing a BRP register wit
- The flat property structure (e.g., `verblijfplaatsStraat` instead of nested `verblijfplaats.straat`) matches how OpenRegister stores object properties in the JSON column. Nested objects can be used but flat is simpler for faceting and search.
- The `partner`, `kinderen`, and `ouders` references use BSN strings that can be resolved within the same register, enabling cross-referencing without requiring UUID joins.
-#### Scenario SEED-001a: BSN 11-proef validation
+#### Scenario: BSN 11-proef validation
-- GIVEN a seed person with `burgerservicenummer` value `"999993653"`
-- WHEN the weighted checksum is calculated: `(9*9 + 9*8 + 9*7 + 9*6 + 9*5 + 3*4 + 6*3 + 5*2 - 3*1)`
-- THEN the result MUST be divisible by 11
-- AND all 25+ seed BSNs MUST pass the 11-proef
-- AND all BSNs MUST start with `9999` (the known-fictional BSN range used by RVIG for testing)
+- **GIVEN** a seed person with `burgerservicenummer` value `"999993653"`
+- **WHEN** the weighted checksum is calculated: `(9*9 + 9*8 + 9*7 + 9*6 + 9*5 + 3*4 + 6*3 + 5*2 - 3*1)`
+- **THEN** the result MUST be divisible by 11
+- **AND** all 25+ seed BSNs MUST pass the 11-proef
+- **AND** all BSNs MUST start with `9999` (the known-fictional BSN range used by RVIG for testing)
-#### Scenario SEED-001b: Family unit consistency
+#### Scenario: Family unit consistency
-- GIVEN the seed data contains the De Vries family:
+- **GIVEN** the seed data contains the De Vries family:
- Jan Albert de Vries (BSN 999993653, born 1985-03-15, man, gehuwd)
- Maria Bakker-de Vries (BSN 999990019, born 1987-11-22, vrouw, gehuwd)
- Sophie de Vries (BSN 999990020, born 2015-06-10, vrouw, ongehuwd)
- Thomas de Vries (BSN 999990021, born 2018-09-03, man, ongehuwd)
-- THEN Jan's `partnerBsn` MUST equal Maria's BSN and vice versa
-- AND Jan's `kinderen` MUST list Sophie and Thomas
-- AND Sophie's `ouders` MUST list Jan and Maria
-- AND all four MUST share the same `verblijfplaatsStraat`, `verblijfplaatsHuisnummer`, `verblijfplaatsPostcode`
+- **THEN** Jan's `partnerBsn` MUST equal Maria's BSN and vice versa
+- **AND** Jan's `kinderen` MUST list Sophie and Thomas
+- **AND** Sophie's `ouders` MUST list Jan and Maria
+- **AND** all four MUST share the same `verblijfplaatsStraat`, `verblijfplaatsHuisnummer`, `verblijfplaatsPostcode`
-#### Scenario SEED-001c: Geographic distribution
+#### Scenario: Geographic distribution
-- GIVEN the 25+ seed persons
-- THEN persons MUST be distributed across at least 5 municipalities: Amsterdam, Utrecht, Rotterdam, Den Haag, Tilburg
-- AND postcodes MUST be realistic for the specified city (e.g., Amsterdam: 10xx, Utrecht: 35xx, Rotterdam: 30xx)
+- **GIVEN** the 25+ seed persons
+- **THEN** persons MUST be distributed across at least 5 municipalities: Amsterdam, Utrecht, Rotterdam, Den Haag, Tilburg
+- **AND** postcodes MUST be realistic for the specified city (e.g., Amsterdam: 10xx, Utrecht: 35xx, Rotterdam: 30xx)
-#### Scenario SEED-001d: Demographic diversity
+#### Scenario: Demographic diversity
-- GIVEN the seed data
-- THEN the following scenarios MUST be covered:
+- **GIVEN** the seed data
+- **THEN** the following scenarios MUST be covered:
- At least 3 married couples with children (family units)
- At least 2 single persons (ongehuwd, no partner)
- At least 1 divorced person (gescheiden)
@@ -130,13 +128,13 @@ The system MUST provide a `brp_register.json` file containing a BRP register wit
- At least 1 person with registered partnership (partnerschap)
- Ages ranging from minors (under 18) to elderly (over 75)
-#### Scenario SEED-001e: BRP person usable as case initiator
+#### Scenario: BRP person usable as case initiator
-- GIVEN BRP person "Petra Jansen" (BSN 999990027)
-- WHEN a Procest case of type "Omgevingsvergunning" is created
-- THEN the person MUST be linkable as case initiator (betrokkene with role "Aanvrager")
-- AND the person's BSN, naam, and verblijfplaats MUST be displayable in the case participants panel
-- AND the person's address MUST resolve to a valid BAG nummeraanduiding
+- **GIVEN** BRP person "Petra Jansen" (BSN 999990027)
+- **WHEN** a Procest case of type "Omgevingsvergunning" is created
+- **THEN** the person MUST be linkable as case initiator (betrokkene with role "Aanvrager")
+- **AND** the person's BSN, naam, and verblijfplaats MUST be displayable in the case participants panel
+- **AND** the person's address MUST resolve to a valid BAG nummeraanduiding
##### Seed Data Requirements Summary
@@ -156,13 +154,12 @@ The system MUST provide a `brp_register.json` file containing a BRP register wit
---
-### REQ-SEED-002: KVK Register (Kamer van Koophandel)
+### Requirement: REQ-SEED-002 — KVK Register (Kamer van Koophandel)
The system MUST provide a `kvk_register.json` file containing a KVK register with a `maatschappelijkeActiviteit` schema and at least 15 fictional business records.
**Feature tier**: MVP
-
##### Register Definition
| Field | Value |
@@ -219,22 +216,22 @@ The system MUST provide a `kvk_register.json` file containing a KVK register wit
| `aantalWerkzamePersonen` | integer | no | no | Employees at this location | `8` |
| `actief` | boolean | yes | yes | Whether the vestiging is active | `true` |
-#### Scenario SEED-002a: Legal form diversity
+#### Scenario: Legal form diversity
-- GIVEN the 15+ seed businesses
-- THEN the following legal forms MUST be represented:
+- **GIVEN** the 15+ seed businesses
+- **THEN** the following legal forms MUST be represented:
- BV (Besloten Vennootschap): at least 4 records
- Eenmanszaak: at least 3 records (with `eigenaarBsn` linking to BRP persons)
- Stichting: at least 2 records
- VOF (Vennootschap onder Firma): at least 1 record
- NV (Naamloze Vennootschap): at least 1 record
- Vereniging: at least 1 record
-- AND at least 1 business MUST have `actief: false` with `datumUitschrijving` set
+- **AND** at least 1 business MUST have `actief: false` with `datumUitschrijving` set
-#### Scenario SEED-002b: SBI code diversity
+#### Scenario: SBI code diversity
-- GIVEN the seed businesses
-- THEN businesses MUST cover at least 8 different SBI top-level sections:
+- **GIVEN** the seed businesses
+- **THEN** businesses MUST cover at least 8 different SBI top-level sections:
- A (Landbouw): e.g., `"0111"` Akkerbouw
- C (Industrie): e.g., `"1071"` Brood en banket
- F (Bouw): e.g., `"4120"` Algemene burgerlijke en utiliteitsbouw
@@ -244,28 +241,28 @@ The system MUST provide a `kvk_register.json` file containing a KVK register wit
- M (Advisering): e.g., `"6920"` Accountancy en belastingadvies
- Q (Zorg): e.g., `"8610"` Ziekenhuizen
-#### Scenario SEED-002c: Cross-register BRP linkage
+#### Scenario: Cross-register BRP linkage
-- GIVEN BRP person "Jan Albert de Vries" (BSN 999993653) is a business owner
-- WHEN the KVK seed data includes an eenmanszaak "De Vries Consultancy"
-- THEN `eigenaarBsn` MUST equal `"999993653"`
-- AND `eigenaarNaam` MUST equal `"J.A. de Vries"`
-- AND `vestigingsadresStraat` + `vestigingsadresPostcode` SHOULD match Jan's BRP `verblijfplaatsStraat` + `verblijfplaatsPostcode` (common for eenmanszaak)
+- **GIVEN** BRP person "Jan Albert de Vries" (BSN 999993653) is a business owner
+- **WHEN** the KVK seed data includes an eenmanszaak "De Vries Consultancy"
+- **THEN** `eigenaarBsn` MUST equal `"999993653"`
+- **AND** `eigenaarNaam` MUST equal `"J.A. de Vries"`
+- **AND** `vestigingsadresStraat` + `vestigingsadresPostcode` SHOULD match Jan's BRP `verblijfplaatsStraat` + `verblijfplaatsPostcode` (common for eenmanszaak)
-#### Scenario SEED-002d: Business with multiple vestigingen
+#### Scenario: Business with multiple vestigingen
-- GIVEN seed business "Bakkerij De Vries B.V." (KVK 90001234)
-- THEN at least 2 vestiging records MUST exist:
+- **GIVEN** seed business "Bakkerij De Vries B.V." (KVK 90001234)
+- **THEN** at least 2 vestiging records MUST exist:
- Hoofdvestiging: Prinsengracht 200, Amsterdam
- Nevenvestiging: Beethovenstraat 42, Amsterdam
-- AND both vestigingen MUST reference the same `kvkNummer`
+- **AND** both vestigingen MUST reference the same `kvkNummer`
-#### Scenario SEED-002e: Business usable as case betrokkene
+#### Scenario: Business usable as case betrokkene
-- GIVEN a KVK business "Architectenbureau Van Dam B.V." (KVK 90005678)
-- WHEN a Procest case of type "Omgevingsvergunning" is created
-- THEN the business MUST be linkable as a case participant (betrokkene with role "Gemachtigde")
-- AND the business's KVK number, handelsnaam, and vestigingsadres MUST be displayable
+- **GIVEN** a KVK business "Architectenbureau Van Dam B.V." (KVK 90005678)
+- **WHEN** a Procest case of type "Omgevingsvergunning" is created
+- **THEN** the business MUST be linkable as a case participant (betrokkene with role "Gemachtigde")
+- **AND** the business's KVK number, handelsnaam, and vestigingsadres MUST be displayable
##### Seed Data Requirements Summary
@@ -285,13 +282,12 @@ The system MUST provide a `kvk_register.json` file containing a KVK register wit
---
-### REQ-SEED-003: BAG Register (Basisregistratie Adressen en Gebouwen)
+### Requirement: REQ-SEED-003 — BAG Register (Basisregistratie Adressen en Gebouwen)
The system MUST provide a `bag_register.json` file containing a BAG register with schemas for `nummeraanduiding`, `openbareRuimte`, `woonplaats`, `verblijfsobject`, and `pand`, with seed data that matches the addresses used in BRP and KVK seed data.
**Feature tier**: MVP
-
##### Register Definition
| Field | Value |
@@ -363,42 +359,42 @@ The system MUST provide a `bag_register.json` file containing a BAG register wit
| `oorspronkelijkBouwjaar` | integer | yes | no | Original construction year | `1895` |
| `oppervlakte` | integer | no | no | Gross surface area in m2 | `450` |
-#### Scenario SEED-003a: BAG addresses match BRP persons
+#### Scenario: BAG addresses match BRP persons
-- GIVEN BRP person Jan de Vries lives at Keizersgracht 100A, 1015AA Amsterdam
-- THEN the BAG MUST contain:
+- **GIVEN** BRP person Jan de Vries lives at Keizersgracht 100A, 1015AA Amsterdam
+- **THEN** the BAG MUST contain:
- A `woonplaats` record for Amsterdam (identificatie `"3594"`)
- An `openbareRuimte` record for Keizersgracht in Amsterdam
- A `nummeraanduiding` with huisnummer 100, huisletter A, postcode 1015AA
- A `verblijfsobject` with `gebruiksdoel` = `"woonfunctie"`, linked to a `pand`
- A `pand` with `oorspronkelijkBouwjaar` and `status` = `"pand in gebruik"`
-#### Scenario SEED-003b: BAG addresses match KVK businesses
+#### Scenario: BAG addresses match KVK businesses
-- GIVEN KVK business "Bakkerij De Vries B.V." at Prinsengracht 200, 1016GS Amsterdam
-- THEN the BAG MUST contain corresponding `nummeraanduiding`, `openbareRuimte`, `verblijfsobject` (gebruiksdoel `"winkelfunctie"`), and `pand` records
-- AND the BAG address components MUST be consistent: `nummeraanduiding.openbareRuimteNaam` = the openbareRuimte name, `nummeraanduiding.woonplaatsNaam` = the woonplaats name
+- **GIVEN** KVK business "Bakkerij De Vries B.V." at Prinsengracht 200, 1016GS Amsterdam
+- **THEN** the BAG MUST contain corresponding `nummeraanduiding`, `openbareRuimte`, `verblijfsobject` (gebruiksdoel `"winkelfunctie"`), and `pand` records
+- **AND** the BAG address components MUST be consistent: `nummeraanduiding.openbareRuimteNaam` = the openbareRuimte name, `nummeraanduiding.woonplaatsNaam` = the woonplaats name
-#### Scenario SEED-003c: Address for DSO vergunningaanvraag
+#### Scenario: Address for DSO vergunningaanvraag
-- GIVEN DSO vergunningaanvraag for a building project at Herengracht 300, 1016CE Amsterdam
-- THEN the BAG MUST contain the corresponding address records
-- AND the `pand` SHOULD have `status` = `"verbouwing pand"` to represent an ongoing building project
-- AND the `verblijfsobject` MUST have `oppervlakte` set (used in legesberekening)
+- **GIVEN** DSO vergunningaanvraag for a building project at Herengracht 300, 1016CE Amsterdam
+- **THEN** the BAG MUST contain the corresponding address records
+- **AND** the `pand` SHOULD have `status` = `"verbouwing pand"` to represent an ongoing building project
+- **AND** the `verblijfsobject` MUST have `oppervlakte` set (used in legesberekening)
-#### Scenario SEED-003d: Multiple residents at one address
+#### Scenario: Multiple residents at one address
-- GIVEN the Jansen family (5 persons) lives at Maliebaan 50, 3581CS Utrecht
-- THEN ONE `nummeraanduiding` record MUST exist for that address
-- AND the `verblijfsobject` `gebruiksdoel` MUST be `"woonfunctie"`
-- AND all 5 BRP persons MUST reference the same address (postcode + huisnummer + straat + woonplaats)
+- **GIVEN** the Jansen family (5 persons) lives at Maliebaan 50, 3581CS Utrecht
+- **THEN** ONE `nummeraanduiding` record MUST exist for that address
+- **AND** the `verblijfsobject` `gebruiksdoel` MUST be `"woonfunctie"`
+- **AND** all 5 BRP persons MUST reference the same address (postcode + huisnummer + straat + woonplaats)
-#### Scenario SEED-003e: Oppervlakte for legesberekening
+#### Scenario: Oppervlakte for legesberekening
-- GIVEN a Procest case of type "Omgevingsvergunning" at Herengracht 300
-- WHEN the case references a BAG verblijfsobject
-- THEN the `oppervlakte` field MUST be a positive integer representing usable floor area in m2
-- AND the value MUST be usable in the legesberekening formula (fee = base + oppervlakte * rate)
+- **GIVEN** a Procest case of type "Omgevingsvergunning" at Herengracht 300
+- **WHEN** the case references a BAG verblijfsobject
+- **THEN** the `oppervlakte` field MUST be a positive integer representing usable floor area in m2
+- **AND** the value MUST be usable in the legesberekening formula (fee = base + oppervlakte * rate)
##### Seed Data Requirements Summary
@@ -412,13 +408,12 @@ The system MUST provide a `bag_register.json` file containing a BAG register wit
---
-### REQ-SEED-004: DSO Register (Digitaal Stelsel Omgevingswet)
+### Requirement: REQ-SEED-004 — DSO Register (Digitaal Stelsel Omgevingswet)
The system MUST provide a `dso_register.json` file containing a DSO register with schemas for `vergunningaanvraag` and `activiteit`, with seed data representing permit applications in the Omgevingswet domain.
**Feature tier**: V1
-
##### Register Definition
| Field | Value |
@@ -466,38 +461,38 @@ The system MUST provide a `dso_register.json` file containing a DSO register wit
| `bevoegdGezag` | string | no | yes | Competent authority type | `"gemeente"` |
| `omschrijving` | string | no | no | Detailed description of the activity | `"Het bouwen van een bouwwerk waarvoor een omgevingsvergunning vereist is"` |
-#### Scenario SEED-004a: Bouwvergunning linked to BAG
+#### Scenario: Bouwvergunning linked to BAG
-- GIVEN a vergunningaanvraag for "Verbouwing woonhuis" at Herengracht 300
-- THEN `locatieBagId` MUST reference a valid BAG `nummeraanduiding` in the BAG seed data
-- AND the `locatieAdres` MUST match the BAG address components
-- AND `initiatiefnemerBsn` MUST reference a valid BRP person
+- **GIVEN** a vergunningaanvraag for "Verbouwing woonhuis" at Herengracht 300
+- **THEN** `locatieBagId` MUST reference a valid BAG `nummeraanduiding` in the BAG seed data
+- **AND** the `locatieAdres` MUST match the BAG address components
+- **AND** `initiatiefnemerBsn` MUST reference a valid BRP person
-#### Scenario SEED-004b: Multiple activities in one application
+#### Scenario: Multiple activities in one application
-- GIVEN a vergunningaanvraag with `activiteiten: ["Bouwen","Kappen","Uitrit aanleggen"]`
-- THEN 3 corresponding `activiteit` records MUST exist in the DSO register
-- AND the `vergunningaanvraag` links to these activities by name
+- **GIVEN** a vergunningaanvraag with `activiteiten: ["Bouwen","Kappen","Uitrit aanleggen"]`
+- **THEN** 3 corresponding `activiteit` records MUST exist in the DSO register
+- **AND** the `vergunningaanvraag` links to these activities by name
-#### Scenario SEED-004c: Various permit types
+#### Scenario: Various permit types
-- GIVEN the seed data
-- THEN the following application types MUST be represented:
+- **GIVEN** the seed data
+- **THEN** the following application types MUST be represented:
- Bouwvergunning (bouwen van een bouwwerk): reguliere procedure
- Milieuvergunning (milieubelastende activiteit): uitgebreide procedure
- Kapvergunning (vellen van houtopstand): reguliere procedure
- Omgevingsplanactiviteit (afwijken van omgevingsplan): reguliere procedure
- Combined application (samenloop): multiple activities in one aanvraag
-- AND at least 1 application MUST have `status` = `"verleend"` with `besluitdatum` set
-- AND at least 1 application MUST have `status` = `"geweigerd"`
+- **AND** at least 1 application MUST have `status` = `"verleend"` with `besluitdatum` set
+- **AND** at least 1 application MUST have `status` = `"geweigerd"`
-#### Scenario SEED-004d: DSO intake to Procest case mapping
+#### Scenario: DSO intake to Procest case mapping
-- GIVEN a DSO vergunningaanvraag with `zaaknummer = "OLO-2026-00001"`
-- WHEN the system maps this to a Procest case
-- THEN the case MUST reference the DSO zaaknummer as external identifier
-- AND the case type MUST map from the DSO procedureType (regulier -> "Omgevingsvergunning regulier")
-- AND the case deadline MUST be calculated from the procedureType (regulier = 8 weeks, uitgebreid = 26 weeks)
+- **GIVEN** a DSO vergunningaanvraag with `zaaknummer = "OLO-2026-00001"`
+- **WHEN** the system maps this to a Procest case
+- **THEN** the case MUST reference the DSO zaaknummer as external identifier
+- **AND** the case type MUST map from the DSO procedureType (regulier -> "Omgevingsvergunning regulier")
+- **AND** the case deadline MUST be calculated from the procedureType (regulier = 8 weeks, uitgebreid = 26 weeks)
##### Seed Data Requirements Summary
@@ -508,13 +503,12 @@ The system MUST provide a `dso_register.json` file containing a DSO register wit
---
-### REQ-SEED-005: ORI Register (Open Raadsinformatie)
+### Requirement: REQ-SEED-005 — ORI Register (Open Raadsinformatie)
The system MUST provide an `ori_register.json` file containing an ORI register with schemas for council meetings, agenda items, motions, votes, council members, and factions, with seed data representing a fictional municipal council.
**Feature tier**: V1
-
##### Register Definition
| Field | Value |
@@ -626,10 +620,10 @@ The system MUST provide an `ori_register.json` file containing an ORI register w
| `coalitie` | boolean | yes | yes | Part of the coalition | `true` |
| `fractievoorzitter` | string | no | no | Chair name | `"Ahmed El Amrani"` |
-#### Scenario SEED-005a: Complete council composition
+#### Scenario: Complete council composition
-- GIVEN the seed data
-- THEN at least 7 fracties MUST exist representing a realistic Dutch council composition:
+- **GIVEN** the seed data
+- **THEN** at least 7 fracties MUST exist representing a realistic Dutch council composition:
- VVD (6 zetels, coalitie)
- GroenLinks (7 zetels, coalitie)
- D66 (5 zetels, coalitie)
@@ -637,30 +631,30 @@ The system MUST provide an `ori_register.json` file containing an ORI register w
- CDA (3 zetels, oppositie)
- SP (3 zetels, oppositie)
- Lokaal Belang (2 zetels, oppositie)
-- AND at least 30 raadslid records MUST exist (sum of all zetels)
-- AND each raadslid MUST reference a valid fractie name
+- **AND** at least 30 raadslid records MUST exist (sum of all zetels)
+- **AND** each raadslid MUST reference a valid fractie name
-#### Scenario SEED-005b: Council meeting with full proceedings
+#### Scenario: Council meeting with full proceedings
-- GIVEN a raadsvergadering "Raadsvergadering 15 januari 2026"
-- THEN the meeting MUST have at least 8 agendapunten
-- AND at least 2 moties MUST be linked (1 aangenomen, 1 verworpen)
-- AND at least 1 amendement MUST be linked
-- AND at least 3 stemmingen MUST be recorded with `stemmenPerFractie` data
-- AND at least 5 documenten MUST be linked to various agendapunten
+- **GIVEN** a raadsvergadering "Raadsvergadering 15 januari 2026"
+- **THEN** the meeting MUST have at least 8 agendapunten
+- **AND** at least 2 moties MUST be linked (1 aangenomen, 1 verworpen)
+- **AND** at least 1 amendement MUST be linked
+- **AND** at least 3 stemmingen MUST be recorded with `stemmenPerFractie` data
+- **AND** at least 5 documenten MUST be linked to various agendapunten
-#### Scenario SEED-005c: Committee meeting
+#### Scenario: Committee meeting
-- GIVEN the seed data
-- THEN at least 1 commissievergadering MUST exist (e.g., "Commissie Ruimte en Wonen")
-- AND the committee meeting MUST have at least 3 agendapunten of type `bespreekstuk` or `informerend`
+- **GIVEN** the seed data
+- **THEN** at least 1 commissievergadering MUST exist (e.g., "Commissie Ruimte en Wonen")
+- **AND** the committee meeting MUST have at least 3 agendapunten of type `bespreekstuk` or `informerend`
-#### Scenario SEED-005d: Stemming with complete fractie breakdown
+#### Scenario: Stemming with complete fractie breakdown
-- GIVEN a stemming on "Motie: Meer groen in de binnenstad"
-- THEN `stemmenPerFractie` MUST contain entries for all 7 fracties
-- AND the sum of `aantalLeden` across fracties MUST equal the total council size (30)
-- AND `voorStemmen` + `tegenStemmen` + `onthouding` MUST equal the total council size
+- **GIVEN** a stemming on "Motie: Meer groen in de binnenstad"
+- **THEN** `stemmenPerFractie` MUST contain entries for all 7 fracties
+- **AND** the sum of `aantalLeden` across fracties MUST equal the total council size (30)
+- **AND** `voorStemmen` + `tegenStemmen` + `onthouding` MUST equal the total council size
##### Seed Data Requirements Summary
@@ -677,82 +671,80 @@ The system MUST provide an `ori_register.json` file containing an ORI register w
---
-### REQ-SEED-006: Cross-Register Relationship Integrity
+### Requirement: REQ-SEED-006 — Cross-Register Relationship Integrity
All cross-register references between seed data MUST be consistent and resolvable.
**Feature tier**: MVP
+#### Scenario: BRP persons live at BAG addresses
-#### Scenario SEED-006a: BRP persons live at BAG addresses
-
-- GIVEN BRP person "Jan de Vries" with `verblijfplaatsStraat` = `"Keizersgracht"`, `verblijfplaatsHuisnummer` = `100`, `verblijfplaatsPostcode` = `"1015AA"`, `verblijfplaatsWoonplaats` = `"Amsterdam"`
-- THEN the BAG register MUST contain:
+- **GIVEN** BRP person "Jan de Vries" with `verblijfplaatsStraat` = `"Keizersgracht"`, `verblijfplaatsHuisnummer` = `100`, `verblijfplaatsPostcode` = `"1015AA"`, `verblijfplaatsWoonplaats` = `"Amsterdam"`
+- **THEN** the BAG register MUST contain:
- A `nummeraanduiding` with matching `openbareRuimteNaam`, `huisnummer`, `postcode`, `woonplaatsNaam`
- A `verblijfsobject` linked to that nummeraanduiding with `gebruiksdoel` = `"woonfunctie"`
-- AND this mapping MUST hold for ALL BRP person addresses
+- **AND** this mapping MUST hold for ALL BRP person addresses
-#### Scenario SEED-006b: KVK businesses have BAG vestigingsadressen
+#### Scenario: KVK businesses have BAG vestigingsadressen
-- GIVEN KVK business "Bakkerij De Vries B.V." at Prinsengracht 200, 1016GS Amsterdam
-- THEN the BAG register MUST contain a `nummeraanduiding` + `verblijfsobject` at that address
-- AND the `verblijfsobject.gebruiksdoel` MUST be appropriate for the business type (e.g., `"winkelfunctie"` for a bakery, `"kantoorfunctie"` for a consultancy)
+- **GIVEN** KVK business "Bakkerij De Vries B.V." at Prinsengracht 200, 1016GS Amsterdam
+- **THEN** the BAG register MUST contain a `nummeraanduiding` + `verblijfsobject` at that address
+- **AND** the `verblijfsobject.gebruiksdoel` MUST be appropriate for the business type (e.g., `"winkelfunctie"` for a bakery, `"kantoorfunctie"` for a consultancy)
-#### Scenario SEED-006c: DSO applications reference BAG and BRP
+#### Scenario: DSO applications reference BAG and BRP
-- GIVEN DSO vergunningaanvraag at Herengracht 300
-- THEN `locatieBagId` MUST reference an existing BAG `nummeraanduiding.identificatie`
-- AND `initiatiefnemerBsn` MUST reference an existing BRP `ingeschrevenPersoon.burgerservicenummer`
+- **GIVEN** DSO vergunningaanvraag at Herengracht 300
+- **THEN** `locatieBagId` MUST reference an existing BAG `nummeraanduiding.identificatie`
+- **AND** `initiatiefnemerBsn` MUST reference an existing BRP `ingeschrevenPersoon.burgerservicenummer`
-#### Scenario SEED-006d: Eenmanszaak owners link BRP to KVK
+#### Scenario: Eenmanszaak owners link BRP to KVK
-- GIVEN KVK eenmanszaak "De Vries Consultancy" with `eigenaarBsn` = `"999993653"`
-- THEN BRP person with BSN `"999993653"` MUST exist
-- AND the business `vestigingsadresStraat`/`vestigingsadresPostcode` SHOULD match the BRP person's `verblijfplaatsStraat`/`verblijfplaatsPostcode` (typical for eenmanszaak)
+- **GIVEN** KVK eenmanszaak "De Vries Consultancy" with `eigenaarBsn` = `"999993653"`
+- **THEN** BRP person with BSN `"999993653"` MUST exist
+- **AND** the business `vestigingsadresStraat`/`vestigingsadresPostcode` SHOULD match the BRP person's `verblijfplaatsStraat`/`verblijfplaatsPostcode` (typical for eenmanszaak)
-#### Scenario SEED-006e: Procest cases can reference all registers
+#### Scenario: Procest cases can reference all registers
-- GIVEN a Procest case of type "Omgevingsvergunning" created from seed data
-- THEN the case SHOULD be linkable to:
+- **GIVEN** a Procest case of type "Omgevingsvergunning" created from seed data
+- **THEN** the case SHOULD be linkable to:
- A BRP person as `betrokkene` (aanvrager) via BSN
- A BAG address as `zaakobject` via nummeraanduiding ID
- A DSO vergunningaanvraag as source via zaaknummer
- An ORI agendapunt (optional, for politically sensitive cases)
-#### Scenario SEED-006f: Pipelinq clients map to KVK
+#### Scenario: Pipelinq clients map to KVK
-- GIVEN a Pipelinq client of type `"organization"` with a KVK number
-- THEN the KVK number MUST match a `maatschappelijkeActiviteit.kvkNummer` in the KVK seed data
-- AND the client `address` SHOULD match the KVK `vestigingsadresStraat` + `vestigingsadresPlaats`
+- **GIVEN** a Pipelinq client of type `"organization"` with a KVK number
+- **THEN** the KVK number MUST match a `maatschappelijkeActiviteit.kvkNummer` in the KVK seed data
+- **AND** the client `address` SHOULD match the KVK `vestigingsadresStraat` + `vestigingsadresPlaats`
---
-### REQ-SEED-007: Seed Data Loading
+### Requirement: REQ-SEED-007 — Seed Data Loading
The register JSON files MUST be loadable by the existing OpenRegister configuration mechanism.
**Feature tier**: MVP
+#### Scenario: Load via CLI command
-#### Scenario SEED-007a: Load via CLI command
-
-- GIVEN the `brp_register.json` file exists in `openregister/lib/Settings/`
-- WHEN the admin runs `docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json`
-- THEN the register, schemas, and seed objects MUST be created in OpenRegister
-- AND seed objects MUST be created from the `components.objects` array in the file
-- AND the command MUST output a summary of created entities
+- **GIVEN** the `brp_register.json` file exists in `openregister/lib/Settings/`
+- **WHEN** the admin runs `docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json`
+- **THEN** the register, schemas, and seed objects MUST be created in OpenRegister
+- **AND** seed objects MUST be created from the `components.objects` array in the file
+- **AND** the command MUST output a summary of created entities
-#### Scenario SEED-007b: Skip if already populated
+#### Scenario: Skip if already populated
-- GIVEN the BRP register already contains person objects
-- WHEN the load command runs again
-- THEN existing data MUST NOT be duplicated
-- AND the command MUST log that seeding was skipped
+- **GIVEN** the BRP register already contains person objects
+- **WHEN** the load command runs again
+- **THEN** existing data MUST NOT be duplicated
+- **AND** the command MUST log that seeding was skipped
-#### Scenario SEED-007c: Seed data uses @self references
+#### Scenario: Seed data uses @self references
-- GIVEN seed objects in the JSON file use the `@self` pattern from opencatalogi
-- THEN each seed object MUST include:
+- **GIVEN** seed objects in the JSON file use the `@self` pattern from opencatalogi
+- **THEN** each seed object MUST include:
```json
{
"@self": {
@@ -765,117 +757,115 @@ The register JSON files MUST be loadable by the existing OpenRegister configurat
...
}
```
-- AND the `slug` MUST be unique within the schema
-- AND the `register` and `schema` values MUST reference definitions in the same file
+- **AND** the `slug` MUST be unique within the schema
+- **AND** the `register` and `schema` values MUST reference definitions in the same file
-#### Scenario SEED-007d: Load via API
+#### Scenario: Load via API
-- GIVEN the `brp_register.json` file content
-- WHEN the admin calls `POST /index.php/apps/openregister/api/registers/import` with the JSON content
-- THEN the register, schemas, and seed objects MUST be created identically to the CLI method
-- AND the API MUST return HTTP 200 with a summary of created entities
+- **GIVEN** the `brp_register.json` file content
+- **WHEN** the admin calls `POST /index.php/apps/openregister/api/registers/import` with the JSON content
+- **THEN** the register, schemas, and seed objects MUST be created identically to the CLI method
+- **AND** the API MUST return HTTP 200 with a summary of created entities
-#### Scenario SEED-007e: Loading order independence
+#### Scenario: Loading order independence
-- GIVEN registers with cross-references (e.g., DSO referencing BAG)
-- WHEN registers are loaded in any order
-- THEN cross-register references MUST be stored as string values (not resolved UUIDs)
-- AND applications MUST resolve references at query time via search by identifier
+- **GIVEN** registers with cross-references (e.g., DSO referencing BAG)
+- **WHEN** registers are loaded in any order
+- **THEN** cross-register references MUST be stored as string values (not resolved UUIDs)
+- **AND** applications MUST resolve references at query time via search by identifier
---
-### REQ-SEED-008: Procest-Specific Seed Data
+### Requirement: REQ-SEED-008 — Procest-Specific Seed Data
The `procest_register.json` MUST include seed data for default case types, status types, and role types to enable immediate case management after installation.
**Feature tier**: MVP
+#### Scenario: Default case types seeded
-#### Scenario SEED-008a: Default case types seeded
-
-- GIVEN a fresh Procest installation with the `procest_register.json` loaded
-- THEN the following case types MUST be available:
+- **GIVEN** a fresh Procest installation with the `procest_register.json` loaded
+- **THEN** the following case types MUST be available:
- "Omgevingsvergunning" (processingDeadline: P56D, published)
- "Subsidieaanvraag" (processingDeadline: P42D, published)
- "Klacht behandeling" (processingDeadline: P42D, published)
- "Melding openbare ruimte" (processingDeadline: P14D, published)
-- AND each case type MUST have linked status types in the correct order
-- AND each case type MUST have at least one result type defined
+- **AND** each case type MUST have linked status types in the correct order
+- **AND** each case type MUST have at least one result type defined
-#### Scenario SEED-008b: Default status types per case type
+#### Scenario: Default status types per case type
-- GIVEN the seeded case type "Omgevingsvergunning"
-- THEN it MUST have the following status types in order:
+- **GIVEN** the seeded case type "Omgevingsvergunning"
+- **THEN** it MUST have the following status types in order:
1. "Ontvangen" (order: 1, isFinal: false)
2. "In behandeling" (order: 2, isFinal: false)
3. "Besluitvorming" (order: 3, isFinal: false)
4. "Afgehandeld" (order: 4, isFinal: true)
-- AND the "Klacht behandeling" case type MUST have:
+- **AND** the "Klacht behandeling" case type MUST have:
1. "Ontvangen" (order: 1)
2. "Onderzoek" (order: 2)
3. "Afgehandeld" (order: 3, isFinal: true)
-#### Scenario SEED-008c: Default role types seeded
+#### Scenario: Default role types seeded
-- GIVEN the seeded case type "Omgevingsvergunning"
-- THEN the following role types MUST be available:
+- **GIVEN** the seeded case type "Omgevingsvergunning"
+- **THEN** the following role types MUST be available:
- "Behandelaar" (handler role)
- "Aanvrager" (initiator role)
- "Gemachtigde" (authorized representative)
- "Technisch adviseur" (advisor)
-- AND these role types MUST be linkable to cases of this type
+- **AND** these role types MUST be linkable to cases of this type
-#### Scenario SEED-008d: Default result types seeded
+#### Scenario: Default result types seeded
-- GIVEN the seeded case type "Omgevingsvergunning"
-- THEN the following result types MUST be available:
+- **GIVEN** the seeded case type "Omgevingsvergunning"
+- **THEN** the following result types MUST be available:
- "Vergunning verleend" (archiveAction: retain, retentionPeriod: P20Y)
- "Vergunning geweigerd" (archiveAction: destroy, retentionPeriod: P10Y)
- "Ingetrokken" (archiveAction: destroy, retentionPeriod: P5Y)
-#### Scenario SEED-008e: Seed data enables immediate demo
+#### Scenario: Seed data enables immediate demo
-- GIVEN all seed data is loaded (procest register + base registers)
-- WHEN a user creates a case of type "Omgevingsvergunning" with title "Verbouwing woonhuis"
-- THEN the case MUST be creatable without additional configuration
-- AND a BRP person MUST be linkable as initiator
-- AND a BAG address MUST be linkable as case object
-- AND the full case lifecycle (status changes, tasks, decisions) MUST be walkable
+- **GIVEN** all seed data is loaded (procest register + base registers)
+- **WHEN** a user creates a case of type "Omgevingsvergunning" with title "Verbouwing woonhuis"
+- **THEN** the case MUST be creatable without additional configuration
+- **AND** a BRP person MUST be linkable as initiator
+- **AND** a BAG address MUST be linkable as case object
+- **AND** the full case lifecycle (status changes, tasks, decisions) MUST be walkable
---
-### REQ-SEED-009: Seed Data Consistency Validation
+### Requirement: REQ-SEED-009 — Seed Data Consistency Validation
The seed data MUST be internally consistent and pass validation checks.
**Feature tier**: MVP
+#### Scenario: No orphan references
-#### Scenario SEED-009a: No orphan references
-
-- GIVEN all seed data across all registers
-- THEN every `partnerBsn` in BRP MUST reference an existing BRP person
-- AND every `kinderen[].bsn` MUST reference an existing BRP person
-- AND every `eigenaarBsn` in KVK MUST reference an existing BRP person
-- AND every `locatieBagId` in DSO MUST reference an existing BAG nummeraanduiding
-- AND every `vergaderingId` in ORI agendapunten MUST reference an existing vergadering
+- **GIVEN** all seed data across all registers
+- **THEN** every `partnerBsn` in BRP MUST reference an existing BRP person
+- **AND** every `kinderen[].bsn` MUST reference an existing BRP person
+- **AND** every `eigenaarBsn` in KVK MUST reference an existing BRP person
+- **AND** every `locatieBagId` in DSO MUST reference an existing BAG nummeraanduiding
+- **AND** every `vergaderingId` in ORI agendapunten MUST reference an existing vergadering
-#### Scenario SEED-009b: Date consistency
+#### Scenario: Date consistency
-- GIVEN all seed data
-- THEN no person MUST have `geboortedatum` in the future
-- AND no person MUST have `overlijdensdatum` before `geboortedatum`
-- AND children MUST be born after both parents
-- AND `datumOprichting` for KVK businesses MUST be before today
-- AND `datumUitschrijving` MUST be after `datumOprichting` when set
+- **GIVEN** all seed data
+- **THEN** no person MUST have `geboortedatum` in the future
+- **AND** no person MUST have `overlijdensdatum` before `geboortedatum`
+- **AND** children MUST be born after both parents
+- **AND** `datumOprichting` for KVK businesses MUST be before today
+- **AND** `datumUitschrijving` MUST be after `datumOprichting` when set
-#### Scenario SEED-009c: Identifier uniqueness
+#### Scenario: Identifier uniqueness
-- GIVEN all seed data within a register
-- THEN every BSN MUST be unique within the BRP register
-- AND every KVK nummer MUST be unique within the KVK register
-- AND every BAG identificatie MUST be unique within the BAG register
-- AND every DSO zaaknummer MUST be unique within the DSO register
+- **GIVEN** all seed data within a register
+- **THEN** every BSN MUST be unique within the BRP register
+- **AND** every KVK nummer MUST be unique within the KVK register
+- **AND** every BAG identificatie MUST be unique within the BAG register
+- **AND** every DSO zaaknummer MUST be unique within the DSO register
---
diff --git a/openspec/changes/base-register-seed-data/tasks.md b/openspec/changes/archive/2026-05-11-base-register-seed-data/tasks.md
similarity index 100%
rename from openspec/changes/base-register-seed-data/tasks.md
rename to openspec/changes/archive/2026-05-11-base-register-seed-data/tasks.md
diff --git a/openspec/changes/bw-parafering/.openspec.yaml b/openspec/changes/archive/2026-05-11-bw-parafering/.openspec.yaml
similarity index 100%
rename from openspec/changes/bw-parafering/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-bw-parafering/.openspec.yaml
diff --git a/openspec/changes/bw-parafering/builds/build.json b/openspec/changes/archive/2026-05-11-bw-parafering/builds/build.json
similarity index 100%
rename from openspec/changes/bw-parafering/builds/build.json
rename to openspec/changes/archive/2026-05-11-bw-parafering/builds/build.json
diff --git a/openspec/changes/bw-parafering/design.md b/openspec/changes/archive/2026-05-11-bw-parafering/design.md
similarity index 100%
rename from openspec/changes/bw-parafering/design.md
rename to openspec/changes/archive/2026-05-11-bw-parafering/design.md
diff --git a/openspec/changes/bw-parafering/proposal.md b/openspec/changes/archive/2026-05-11-bw-parafering/proposal.md
similarity index 100%
rename from openspec/changes/bw-parafering/proposal.md
rename to openspec/changes/archive/2026-05-11-bw-parafering/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-bw-parafering/specs/bw-parafering/spec.md b/openspec/changes/archive/2026-05-11-bw-parafering/specs/bw-parafering/spec.md
new file mode 100644
index 00000000..e81cf6a0
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-bw-parafering/specs/bw-parafering/spec.md
@@ -0,0 +1,515 @@
+---
+status: implemented
+---
+# B&W Parafering & Besluitvorming Specification
+
+## Purpose
+
+B&W parafering covers the ambtelijk (civil servant) workflow for preparing, reviewing, and approving proposals before they reach the College van B&W for formal decision-making. The bestuurlijk (political) part -- agenda management, vergadering, and besluitenlijst -- is handled by external RIS systems (iBabs, NotuBiz). This spec covers the ambtelijk workflow and the connector to the RIS.
+
+**Tender demand**: Found in 20+ tenders (29% of all, higher among generic zaaksysteem tenders). B&W besluitvorming is the #6 Nice-to-have but weighs heavily in scoring (typically 3-8% of total score, up to 68 points).
+**Standards**: BPMN 2.0 (process modeling), ZGW Besluiten API, ZDS (Zaak-Document Services for legacy RIS), CMMN 1.1 (HumanTask for parafering steps)
+**Feature tier**: V1 (ambtelijk parafering, sequential routing, audit trail), V2 (parallel parafering, mobile parafering, iBabs/NotuBiz connector, vergaderbeheer)
+
+**Competitive context**: Dimpact ZAC implements decision management via the ZGW BRC API with besluittype validation, publication date handling, and document linking. ZAC does NOT include B&W parafering workflow -- that is handled externally. Flowable's CMMN engine can model parafeerroutes as sequential/parallel HumanTasks with configurable completion rules. ArkCase and CaseFabric both provide full approval workflows with configurable routing. Procest should implement parafering as OpenRegister objects with task-based routing, leveraging the existing task management infrastructure.
+
+## Standard Workflow (10-Step Process)
+
+Reconstructed from 20+ tender analyses, this is the standard B&W besluitvormingsproces:
+
+| Step | Actor | Action | System |
+|------|-------|--------|--------|
+| 1 | Steller | Creates advies/voorstel from case context | Procest |
+| 2 | Adviseur(s) | Provide internal advice on the voorstel | Procest |
+| 3 | Parafeerder(s) | Paraferen the voorstel (sequential or parallel) | Procest |
+| 4 | Manager/Afdelingshoofd | Accordeert the voorstel | Procest |
+| 5 | Portefeuillehouder (wethouder) | Accordeert the voorstel | Procest |
+| 6 | Secretariaat/Agendacommissie | Reviews quality, places on agenda | Procest + RIS |
+| 7 | BMO/Kwaliteitstoets | Technical and legal quality check | Procest |
+| 8 | College B&W | Treats voorstel (hamerstuk or bespreekstuk) | RIS (iBabs/NotuBiz) |
+| 9 | Besluitenlijst | Decision recorded and published | RIS -> Procest |
+| 10 | Archivering | Besluit linked back to case and archived | Procest |
+
+**Key principle**: Steps 1-7 are ambtelijk (in Procest). Steps 8-9 are bestuurlijk (in the RIS). Step 10 bridges back.
+
+### OpenRegister Schema Model
+
+```
+voorstel:
+ case: reference # -> case
+ type: enum # dt_advies | collegeadvies | raadsvoorstel
+ onderwerp: string # from case title
+ steller: string # user UID who created
+ afdeling: string # department
+ portefeuillehouder: string # wethouder UID
+ status: enum # concept | in_parafering | ter_accordering | geaccordeerd | aangeboden | besloten | gearchiveerd | teruggestuurd
+ parafeerroute: reference # -> parafeerroute
+ currentStep: integer # current step number in route
+ document: string # Nextcloud file ID for the voorstel document
+ bijlagen: array # Nextcloud file IDs for attachments
+ behandeling: enum # hamerstuk | bespreekstuk
+ createdAt: datetime
+ updatedAt: datetime
+
+parafeerroute:
+ name: string # "Collegeadvies - Omgevingsvergunning"
+ caseType: reference # -> caseType (optional, for default route)
+ voorstelType: enum # dt_advies | collegeadvies | raadsvoorstel
+ steps: array # ordered list of parafeerstap
+
+parafeerstap:
+ order: integer # 1, 2, 3...
+ type: enum # advies | parafering | accordering
+ actor: string # user UID or role name
+ actorType: enum # user | group | role
+ parallel: boolean # if true, all actors in this step must complete
+ parallelActors: array # list of user UIDs for parallel parafering
+ completionRule: enum # all | any (for parallel: all must complete, or any one)
+ mandatory: boolean # if false, step can be skipped
+
+parafeeractie:
+ voorstel: reference # -> voorstel
+ step: integer # step number
+ actor: string # user UID who performed action
+ actorType: enum # user | delegate
+ onBehalfOf: string # user UID if acting on behalf of someone
+ action: enum # parafered | returned | advised | skipped
+ comment: string # optional comment
+ advice: string # for advisory steps
+ timestamp: datetime
+ mandate: string # mandate reference if acting on behalf
+```
+
+## ADDED Requirements
+---
+
+### Requirement: REQ-BW-01 — Voorstel Creation from Case
+
+The system MUST support creating a B&W-voorstel (college proposal) from within a case context.
+
+**Feature tier**: V1
+
+#### Scenario: Create college voorstel
+
+- **GIVEN** a case "Bestemmingsplan Centrum" at status "Besluitvorming"
+- **WHEN** the steller clicks "Nieuw B&W-voorstel" in the case dashboard
+- **THEN** the system MUST create a voorstel object linked to the case in OpenRegister
+- **AND** the voorstel MUST include: onderwerp (from case title), steller (current user), afdeling (from case type config), portefeuillehouder (from case type config)
+- **AND** a document template "Collegeadvies" MUST be generated via Docudesk with case data pre-filled
+- **AND** the case documents MUST be available as bijlagen to the voorstel
+
+#### Scenario: Voorstel types
+
+- **GIVEN** voorstel types: "DT-advies" (directieteam), "Collegeadvies", "Raadsvoorstel"
+- **WHEN** the steller creates a new voorstel
+- **THEN** the steller MUST select the voorstel type from a dropdown
+- **AND** the parafeerroute MUST be loaded from the case type configuration for that voorstel type
+- **AND** the selected type MUST determine which document template is used
+
+#### Scenario: Voorstel from case dashboard panel
+
+- **GIVEN** a case dashboard with a "B&W Voorstellen" panel
+- **WHEN** the panel is empty (no voorstellen yet)
+- **THEN** the panel MUST show: "Geen voorstellen" with a "Nieuw voorstel" button
+- **AND** when a voorstel exists, it MUST show: type, status, current parafeeerstap, steller
+
+#### Scenario: Multiple voorstellen per case
+
+- **GIVEN** a case with an existing "DT-advies" voorstel (status: besloten)
+- **WHEN** the steller creates a new "Collegeadvies" voorstel
+- **THEN** both voorstellen MUST be visible in the case dashboard panel
+- **AND** the history of the DT-advies MUST remain accessible
+
+#### Scenario: Pre-fill voorstel from case data
+
+- **GIVEN** a case with properties: bouwkosten, locatie, aanvrager
+- **WHEN** creating a collegeadvies voorstel
+- **THEN** the Docudesk template MUST pre-fill: onderwerp, zaaknummer, locatie, aanvrager, bouwkosten
+- **AND** the steller MUST be able to edit the generated document before submitting for parafering
+
+---
+
+### Requirement: REQ-BW-02 — Configurable Parafeerroute
+
+The system MUST support configurable parafeerroutes per case type and voorstel type. The route defines who must paraferen/accorderen and in what order.
+
+**Feature tier**: V1
+
+#### Scenario: Sequential parafering
+
+- **GIVEN** a parafeerroute for "Collegeadvies" on case type "Omgevingsvergunning":
+ 1. Adviseur vakinhoud (advisory)
+ 2. Juridisch adviseur (advisory)
+ 3. Teamleider (parafering)
+ 4. Afdelingshoofd (parafering)
+ 5. Portefeuillehouder (accordering)
+- **WHEN** the steller submits the voorstel for parafering
+- **THEN** the system MUST route to step 1 first
+- **AND** each step MUST complete before the next step is activated
+- **AND** each actor MUST receive a Nextcloud notification and a task in their "Mijn taken" list
+
+#### Scenario: Parallel parafering
+
+- **GIVEN** a parafeerroute with step 3 configured as parallel: [Teamleider A, Teamleider B]
+- **AND** completionRule = "all"
+- **WHEN** step 3 is reached
+- **THEN** both Teamleider A and Teamleider B MUST receive the voorstel simultaneously
+- **AND** the step completes when ALL parallel actors have parafered
+- **AND** the voorstel status MUST show "Wacht op 2 parafen" until both complete
+
+#### Scenario: Override parafeerroute
+
+- **GIVEN** the standard route requires 5 steps
+- **AND** an authorized manager wants to skip the vakinhoudelijk advies step
+- **WHEN** the manager removes step 1 from the route for this specific voorstel
+- **THEN** the system MUST allow the modification
+- **AND** the audit trail MUST record: "Parafeerroute aangepast: stap 'Adviseur vakinhoud' overgeslagen door [manager], reden: [text]"
+- **AND** a reason MUST be mandatory when skipping steps
+
+#### Scenario: Add ad-hoc step
+
+- **GIVEN** a voorstel at step 2 of 5
+- **WHEN** the steller or manager adds an ad-hoc advisory step "Financieel adviseur" between step 2 and 3
+- **THEN** the route MUST be adjusted: steps 3-5 become 4-6, new step 3 is the ad-hoc step
+- **AND** the audit trail MUST record: "Stap toegevoegd: 'Financieel adviseur' door [user]"
+
+#### Scenario: Admin route configuration
+
+- **GIVEN** the beheerder opens Procest admin settings
+- **WHEN** navigating to "Parafeerroutes" configuration
+- **THEN** the beheerder MUST be able to:
+ - Create a new route with named steps
+ - Assign each step a type (advies/parafering/accordering), actor type (user/group/role), and parallel flag
+ - Link the route to a case type and voorstel type
+ - Set a route as the default for a case type
+
+---
+
+### Requirement: REQ-BW-03 — Parafering Actions
+
+Each actor in the parafeerroute MUST be able to perform specific actions on the voorstel.
+
+**Feature tier**: V1
+
+#### Scenario: Paraferen (approve)
+
+- **GIVEN** a voorstel at step "Teamleider" assigned to "Jan de Vries"
+- **WHEN** Jan clicks "Paraferen" in his task or in the voorstel detail view
+- **THEN** the system MUST record a parafeeractie: actor=Jan, action=parafered, timestamp=now
+- **AND** the voorstel MUST advance to the next step
+- **AND** Jan MUST NOT be able to paraferen again on this voorstel
+- **AND** a notification MUST be sent to the next actor in the route
+
+#### Scenario: Return with comments (terugsturen)
+
+- **GIVEN** a voorstel at step "Afdelingshoofd"
+- **WHEN** the afdelingshoofd clicks "Terugsturen" with comment "Financiele paragraaf ontbreekt"
+- **THEN** the voorstel MUST be returned to the steller (status: teruggestuurd)
+- **AND** the comment MUST be visible to the steller in the voorstel detail
+- **AND** the audit trail MUST record the return with reason
+- **AND** the steller MUST be notified: "Voorstel teruggestuurd door [afdelingshoofd]: Financiele paragraaf ontbreekt"
+- **AND** the steller MUST be able to edit the document and resubmit (resumes from the returning step)
+
+#### Scenario: Adviseren (non-binding opinion)
+
+- **GIVEN** a voorstel at an advisory step (not parafering)
+- **WHEN** the adviseur submits advice: "Akkoord, mits bouwkosten worden gecontroleerd"
+- **THEN** the advice MUST be attached to the voorstel as a parafeeractie with action=advised
+- **AND** the voorstel MUST advance to the next step (advice is non-blocking)
+- **AND** the steller and subsequent parafeerders MUST be able to see the advice in the voorstel detail
+
+#### Scenario: Paraferen namens (on behalf of)
+
+- **GIVEN** portefeuillehouder wethouder Van Dam is unavailable
+- **AND** secretaresse Bakker has mandate to paraferen namens Van Dam (configured in admin settings)
+- **WHEN** Bakker opens the voorstel task
+- **THEN** Bakker MUST see an option "Paraferen namens Van Dam"
+- **AND** the audit trail MUST record: "Geparafeerd door Bakker namens Van Dam (mandaat: [reference])"
+
+#### Scenario: View voorstel document during parafering
+
+- **GIVEN** a parafeerder receives a voorstel task
+- **WHEN** opening the voorstel detail view
+- **THEN** the voorstel document MUST be viewable inline (PDF preview or document viewer)
+- **AND** all bijlagen MUST be listed and downloadable
+- **AND** previous advice from earlier steps MUST be visible
+- **AND** the parafering history MUST show which steps are completed
+
+---
+
+### Requirement: REQ-BW-04 — Mobile Parafering
+
+The system MUST support parafering from mobile devices (tablets, smartphones) for bestuurders who are frequently on the move.
+
+**Feature tier**: V2
+
+#### Scenario: Paraferen on tablet
+
+- **GIVEN** wethouder Van Dam viewing pending voorstellen on a tablet
+- **WHEN** Van Dam opens voorstel "Bestemmingsplan Centrum"
+- **THEN** the voorstel document and bijlagen MUST be readable on the tablet
+- **AND** "Paraferen" and "Terugsturen" buttons MUST be accessible
+- **AND** the UI MUST be responsive (no pinch-to-zoom required for core actions)
+- **AND** touch targets MUST be at least 44x44px per WCAG AA
+
+#### Scenario: Offline document access
+
+- **GIVEN** a wethouder preparing for a vergadering without reliable internet
+- **WHEN** the wethouder opens the Nextcloud mobile app
+- **THEN** voorstel documents that were previously viewed MUST be available offline (Nextcloud Files offline sync)
+- **AND** parafering actions MUST queue and sync when connectivity returns
+
+#### Scenario: Push notification for pending parafering
+
+- **GIVEN** a new voorstel awaiting Van Dam's parafering
+- **WHEN** the voorstel reaches Van Dam's step
+- **THEN** a push notification MUST be sent via the Nextcloud mobile app: "Nieuw voorstel ter parafering: [onderwerp]"
+- **AND** tapping the notification MUST open the voorstel detail
+
+---
+
+### Requirement: REQ-BW-05 — RIS Connector (iBabs/NotuBiz)
+
+The system MUST support pushing approved voorstellen to the external RIS for bestuurlijke behandeling, and receiving besluiten back.
+
+**Feature tier**: V2
+
+#### Scenario: Push voorstel to iBabs
+
+- **GIVEN** a voorstel that has completed all ambtelijke parafering steps (status: geaccordeerd)
+- **AND** the secretariaat marks it for agendering with behandeling type (hamerstuk/bespreekstuk)
+- **WHEN** the secretariaat clicks "Aanbieden aan iBabs"
+- **THEN** the system MUST push via iBabs API: voorstel document, bijlagen, metadata (onderwerp, portefeuillehouder, hamerstuk/bespreekstuk)
+- **AND** the voorstel status MUST change to "Aangeboden aan college"
+- **AND** the push status MUST be tracked: "Verstuurd", "Ontvangen", "Fout"
+
+#### Scenario: Receive besluit from iBabs
+
+- **GIVEN** a voorstel treated in the college vergadering
+- **AND** the besluit is recorded in iBabs
+- **WHEN** the besluit is synced back to Procest (via API polling or webhook through OpenConnector)
+- **THEN** the system MUST create a Besluit object linked to the case via the BRC controller
+- **AND** the case timeline MUST show: "College besluit: [besluit tekst]"
+- **AND** the voorstel status MUST change to "Besloten"
+- **AND** the besluit document from iBabs MUST be stored in Nextcloud Files linked to the case
+
+#### Scenario: NotuBiz connector
+
+- **GIVEN** a municipality using NotuBiz instead of iBabs
+- **WHEN** the connector is configured for NotuBiz in OpenConnector
+- **THEN** the same push/receive flow MUST work via NotuBiz API or ZIP(XML+PDF) exchange
+- **AND** the system MUST support both iBabs and NotuBiz as pluggable RIS adapters
+
+#### Scenario: RIS connector not configured
+
+- **GIVEN** no RIS connector is configured
+- **WHEN** the secretariaat views the voorstel
+- **THEN** the "Aanbieden aan RIS" button MUST be hidden
+- **AND** a manual "Markeer als besloten" button MUST allow recording the besluit without a RIS
+
+---
+
+### Requirement: REQ-BW-06 — Parafering Audit Trail
+
+The system MUST maintain an immutable audit trail of all parafering actions. This is a legal requirement -- the trail must be reconstructable for accountability and Archiefwet compliance.
+
+**Feature tier**: V1
+
+#### Scenario: Complete audit trail
+
+- **GIVEN** a voorstel that has passed through 5 parafering steps
+- **WHEN** an auditor reviews the voorstel
+- **THEN** the audit trail MUST show for each step: step number, step type (advies/parafering/accordering), actor, action (parafered/returned/advised/skipped), timestamp, comments
+- **AND** no entries MAY be deleted or modified after recording (immutable)
+- **AND** the trail MUST be exportable as PDF for archival
+
+#### Scenario: Route modification audit
+
+- **GIVEN** a parafeerroute was modified (step skipped or added)
+- **THEN** the audit trail MUST include route modification events: who modified, what changed, reason provided
+- **AND** the original route definition MUST be preserved alongside the modified version
+
+#### Scenario: Delegation audit
+
+- **GIVEN** parafering was performed by a delegate (namens)
+- **THEN** the audit trail MUST clearly distinguish: "Geparafeerd door [delegate] namens [principal] op basis van mandaat [reference]"
+- **AND** both the delegate and principal MUST be searchable in audit queries
+
+---
+
+### Requirement: REQ-BW-07 — Parafering Dashboard
+
+The system MUST provide an overview of all active voorstellen and their parafering status.
+
+**Feature tier**: V1
+
+#### Scenario: Secretariaat overview
+
+- **GIVEN** 8 active voorstellen in various stages of parafering
+- **WHEN** the secretariaat views the parafering dashboard
+- **THEN** each voorstel MUST show: onderwerp, current step, waiting actor, days in current step, overall progress (step 3/5)
+- **AND** voorstellen overdue on any step (waiting > configured threshold) MUST be highlighted in orange/red
+- **AND** the secretariaat MUST be able to send reminders to actors who have not yet parafered
+
+#### Scenario: Personal parafering inbox
+
+- **GIVEN** wethouder Van Dam has 3 voorstellen awaiting his parafering
+- **WHEN** Van Dam opens his parafering inbox (in My Work or as separate view)
+- **THEN** the 3 voorstellen MUST be listed with: onderwerp, case reference, steller, waiting since
+- **AND** each item MUST be actionable directly (paraferen/terugsturen without opening full detail)
+
+#### Scenario: Pipeline visualization
+
+- **GIVEN** 12 voorstellen in the parafering pipeline
+- **WHEN** the secretariaat views the pipeline
+- **THEN** a Kanban-style board MUST show columns per parafering phase: Concept, In parafering, Ter accordering, Geaccordeerd, Aangeboden aan college, Besloten
+- **AND** each voorstel MUST be a card showing: onderwerp, steller, days in phase
+
+#### Scenario: Send reminder
+
+- **GIVEN** a voorstel has been waiting at step "Afdelingshoofd" for 5 days (threshold: 3 days)
+- **WHEN** the secretariaat clicks "Herinnering sturen"
+- **THEN** a Nextcloud notification MUST be sent to the afdelingshoofd: "Voorstel '[onderwerp]' wacht op uw parafering (5 dagen)"
+- **AND** the reminder MUST be logged in the audit trail
+
+---
+
+### Requirement: REQ-BW-08 — Voorstel Detail View
+
+The system MUST provide a dedicated detail view for voorstellen, showing the document, parafering progress, and actions.
+
+**Feature tier**: V1
+
+#### Scenario: View voorstel detail
+
+- **GIVEN** a voorstel "Collegeadvies Bestemmingsplan Centrum"
+- **WHEN** any authorized user opens the voorstel detail
+- **THEN** the view MUST show:
+ - Header: onderwerp, type, steller, afdeling, status
+ - Document viewer: inline preview of the voorstel document
+ - Bijlagen: list of attached documents
+ - Parafering progress: visual step indicator showing completed/current/future steps
+ - Action history: all parafeeracties with timestamps, actors, comments
+ - Case reference: link back to the parent case
+
+#### Scenario: Action buttons per role
+
+- **GIVEN** the current user is the active parafeerder at the current step
+- **THEN** the voorstel detail MUST show action buttons: "Paraferen", "Terugsturen"
+- **AND** if the step type is "advies", the button MUST be "Adviseren" instead of "Paraferen"
+- **AND** if the user is NOT the active actor, action buttons MUST be hidden
+
+#### Scenario: Progress timeline
+
+- **GIVEN** a voorstel with 5 steps where steps 1-3 are completed, step 4 is active, step 5 is pending
+- **THEN** the progress indicator MUST show:
+ - Steps 1-3: green checkmark with actor name and date
+ - Step 4: blue active indicator with actor name and "Wachtend"
+ - Step 5: grey pending indicator with actor name
+
+---
+
+### Requirement: REQ-BW-09 — Besluit Registration
+
+When a besluit is received (from RIS or manually), the system MUST create a formal besluit record linked to the case via the ZGW Besluiten API pattern.
+
+**Feature tier**: V1
+
+#### Scenario: Manual besluit registration
+
+- **GIVEN** a voorstel has been treated by the college (outside Procest)
+- **WHEN** the secretariaat clicks "Besluit registreren" and enters: besluit tekst, ingangsdatum, besluittype
+- **THEN** a besluit object MUST be created via the BRC controller pattern
+- **AND** the besluit MUST be linked to the case (zaak-besluit relation)
+- **AND** the case activity timeline MUST show: "Besluit vastgesteld: [tekst]"
+
+#### Scenario: Besluit with documents
+
+- **GIVEN** a besluit is being registered
+- **WHEN** the secretariaat attaches the besluitbrief and besluitenlijst
+- **THEN** the documents MUST be linked as besluitinformatieobjecten (via `BrcController`)
+- **AND** the documents MUST be stored in Nextcloud Files under the case folder
+
+#### Scenario: Withdraw besluit
+
+- **GIVEN** a besluit has been registered but needs to be withdrawn
+- **WHEN** the secretariaat clicks "Intrekken" with reason "Ingetrokken door overheid"
+- **THEN** the besluit vervaldatum MUST be set to today
+- **AND** the vervalreden MUST be recorded
+- **AND** the case timeline MUST show: "Besluit ingetrokken: [reden]"
+
+---
+
+### Requirement: REQ-BW-10 — Archiving
+
+Completed voorstellen and besluiten MUST be archived according to the Archiefwet requirements.
+
+**Feature tier**: V1
+
+#### Scenario: Archive voorstel after besluit
+
+- **GIVEN** a voorstel has status "Besloten" with a linked besluit
+- **WHEN** the archiving process runs
+- **THEN** the voorstel document, all bijlagen, the audit trail, and the besluit document MUST be packaged
+- **AND** the package MUST be stored in the case's archive folder in Nextcloud Files
+- **AND** the voorstel status MUST change to "Gearchiveerd"
+
+#### Scenario: Archive retention metadata
+
+- **GIVEN** an archived voorstel
+- **THEN** the archive record MUST include: bewaarplaats (Nextcloud Files path), bewaartermijn (from case type config), vernietigingsdatum (calculated from bewaar termijn)
+- **AND** the metadata MUST be queryable for future destruction scheduling
+
+## Dependencies
+
+- **Case Management spec** (`../case-management/spec.md`): Voorstellen originate from cases.
+- **Case Dashboard View spec** (`../case-dashboard-view/spec.md`): Voorstel panel on case detail.
+- **Roles & Decisions spec** (`../roles-decisions/spec.md`): Besluiten are created when the college decides.
+- **Task Management spec** (`../task-management/spec.md`): Parafering steps create tasks for actors.
+- **OpenRegister**: Voorstellen, parafeerroutes, parafeeracties stored as OpenRegister objects.
+- **OpenConnector**: iBabs API, NotuBiz API adapters for RIS integration.
+- **Docudesk**: Document templates for collegeadvies, raadsvoorstel.
+- **BrcController**: ZGW Besluiten API pattern for besluit registration (`lib/Controller/BrcController.php`).
+- **NotificatieService**: Nextcloud notifications for parafering tasks (`lib/Service/NotificatieService.php`).
+
+### Current Implementation Status
+
+**Not yet implemented.** No parafering, voorstel, or B&W decision-related code exists in the Procest codebase. There are no schemas for voorstel, parafeerroute, or parafeeractie in `procest_register.json`. No Vue components for parafering workflows exist.
+
+**Foundation available:**
+- Task management infrastructure (`src/views/tasks/`, `src/services/taskApi.js`, `src/utils/taskLifecycle.js`) provides a model for parafering steps (each step could be modeled as a task with custom type "parafering").
+- The `decision` schema exists in `SettingsService::SLUG_TO_CONFIG_KEY` (config key `decision_schema`), providing a foundation for recording besluiten.
+- The `decisionType` schema exists for typing decisions.
+- ZGW Besluiten API controller (`lib/Controller/BrcController.php`) handles besluit CRUD via ZGW API endpoints, including cross-register OIO sync and cascade delete.
+- Activity timeline component (`src/views/cases/components/ActivityTimeline.vue`) could display parafering events.
+- Nextcloud notification infrastructure is available via the `NotificatieService` (`lib/Service/NotificatieService.php`).
+- `CnDetailCard` component pattern for the voorstel panel on the case dashboard.
+- Case detail view (`CaseDetail.vue`) provides the mounting point for the B&W voorstellen panel.
+
+**Partial implementations:** The `BrcController` and decision schemas provide the data model foundation for step 9-10 (besluit registration and archiving).
+
+### Standards & References
+
+- **BPMN 2.0**: Process modeling standard for sequential/parallel parafeerroutes.
+- **CMMN 1.1**: HumanTask concept maps to parafering steps. Each step is a human task in a case plan model.
+- **ZGW Besluiten API (VNG)**: For recording formal besluiten (decisions) linked to cases. Procest's `BrcController` implements this standard.
+- **Awb (Algemene wet bestuursrecht)**: Legal framework for administrative decision-making.
+- **iBabs API**: Commercial API for raadsinformatiesysteem (council information system). REST-based with JSON payloads.
+- **NotuBiz API**: Alternative RIS platform API. Supports ZIP(XML+PDF) exchange format.
+- **GEMMA**: B&W besluitvormingsproces is a standard reference process in GEMMA zaakgericht werken.
+- **Archiefwet**: Legal requirements for archiving besluiten and voorstel documents.
+- **BIO**: Security requirements for handling voorstellen containing confidential information.
+
+### Specificity Assessment
+
+This spec is well-structured with a clear 10-step process model, defined OpenRegister schemas, and feature tier separation (V1/V2). The scenarios are detailed with concrete actor/action/system descriptions.
+
+**Strengths:** Clear process model with 10 steps, OpenRegister schema definitions for voorstel/parafeerroute/parafeeractie, concrete delegation scenario (namens), sequential and parallel parafering, admin route configuration, audit trail requirements, RIS connector patterns.
+
+**Resolved ambiguities:**
+- Parafeerroutes are stored as OpenRegister objects (not n8n workflows), enabling version tracking and admin UI.
+- The parafering dashboard is a separate navigation item (not a dashboard tab), with both secretariaat overview and personal inbox views.
+- Unavailable actors without delegates trigger escalation to the secretariaat after a configurable waiting period.
+- iBabs integration uses REST API via OpenConnector; NotuBiz supports both API and ZIP exchange.
+- Mandate/delegation is configured in admin settings and recorded in the audit trail with mandate reference.
+- Parallel parafering supports both "all" and "any" completion rules.
diff --git a/openspec/changes/bw-parafering/tasks.md b/openspec/changes/archive/2026-05-11-bw-parafering/tasks.md
similarity index 100%
rename from openspec/changes/bw-parafering/tasks.md
rename to openspec/changes/archive/2026-05-11-bw-parafering/tasks.md
diff --git a/openspec/changes/case-dashboard-view/builds/build.json b/openspec/changes/archive/2026-05-11-case-dashboard-view/builds/build.json
similarity index 100%
rename from openspec/changes/case-dashboard-view/builds/build.json
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/builds/build.json
diff --git a/openspec/changes/case-dashboard-view/delta-spec.md b/openspec/changes/archive/2026-05-11-case-dashboard-view/delta-spec.md
similarity index 100%
rename from openspec/changes/case-dashboard-view/delta-spec.md
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/delta-spec.md
diff --git a/openspec/changes/case-dashboard-view/design.md b/openspec/changes/archive/2026-05-11-case-dashboard-view/design.md
similarity index 100%
rename from openspec/changes/case-dashboard-view/design.md
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/design.md
diff --git a/openspec/changes/case-dashboard-view/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-case-dashboard-view/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/case-dashboard-view/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/case-dashboard-view/pipeline-logs/build.2.jsonl.gz b/openspec/changes/archive/2026-05-11-case-dashboard-view/pipeline-logs/build.2.jsonl.gz
similarity index 100%
rename from openspec/changes/case-dashboard-view/pipeline-logs/build.2.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/pipeline-logs/build.2.jsonl.gz
diff --git a/openspec/changes/case-dashboard-view/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-case-dashboard-view/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/case-dashboard-view/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/case-dashboard-view/proposal.md b/openspec/changes/archive/2026-05-11-case-dashboard-view/proposal.md
similarity index 100%
rename from openspec/changes/case-dashboard-view/proposal.md
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-case-dashboard-view/specs/case-dashboard-view/spec.md b/openspec/changes/archive/2026-05-11-case-dashboard-view/specs/case-dashboard-view/spec.md
new file mode 100644
index 00000000..748d212b
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-case-dashboard-view/specs/case-dashboard-view/spec.md
@@ -0,0 +1,100 @@
+---
+status: implemented
+---
+# case-dashboard-view Specification
+
+## Purpose
+Deliver a polished, responsive, and accessible case detail dashboard that surfaces all relevant case context (status, deadlines, activity, tasks, documents, participants) on a single screen. This change closes the remaining MVP gaps in the existing `case-dashboard-view` capability: tablet/print rendering, per-panel skeleton loading, and a graceful 404 state for missing cases.
+
+## Context
+`CaseDetail.vue` already renders a two-column dashboard composed of panel cards (status timeline, info, deadline, activity, tasks, documents, participants). Tender demos and on-call inspections require the same screen to work on tablets, print cleanly, and signal loading state per-card instead of via a single page-level spinner. The capability is implemented end-to-end in code; this spec documents the contract.
+
+## ADDED Requirements
+### Requirement: REQ-CDV — 01c: Case Not Found State
+The dashboard MUST display a localized 404 empty state when the requested case cannot be loaded.
+
+#### Scenario: Unknown case identifier
+- **GIVEN** a user navigates to `/cases/does-not-exist`
+- **WHEN** the fetch resolves with no case object
+- **THEN** the view MUST render an `NcEmptyContent` with title "Zaak niet gevonden"
+- **AND** a "Terug naar overzicht" button MUST be visible
+- **AND** clicking the button MUST navigate the user to the case list view
+
+#### Scenario: Case fetch error
+- **GIVEN** the backend returns an error while loading a case
+- **THEN** the empty state MUST be shown instead of a broken panel layout
+- **AND** the action buttons in the page header MUST be hidden
+
+### Requirement: REQ-CDV — 01d: Per-Panel Skeleton Loading
+The dashboard MUST render a skeleton placeholder for each panel card while case data is loading.
+
+#### Scenario: Initial render with no data
+- **GIVEN** a user opens a case detail page for the first time
+- **WHEN** the case data has not yet resolved
+- **THEN** each panel card (status timeline, info, deadline, activity, tasks, documents, participants) MUST render a skeleton placeholder with animated bars
+- **AND** no global page-level spinner MUST be shown
+
+#### Scenario: Skeleton replaced on data ready
+- **GIVEN** the skeleton placeholders are visible
+- **WHEN** the case data resolves
+- **THEN** each skeleton MUST be replaced by its real panel content in place
+- **AND** the page MUST NOT flicker or fully re-mount
+
+### Requirement: REQ-CDV — 07b: Tablet Layout
+The dashboard MUST adapt to a single-column layout at tablet viewport widths.
+
+#### Scenario: Viewport at or below 1200px
+- **GIVEN** a user views the dashboard on a viewport of 1200px or narrower
+- **THEN** the panels MUST stack vertically in a single column
+- **AND** the order MUST be: status timeline, case info, deadline, activity, tasks, documents, participants
+- **AND** no horizontal scrollbar MUST appear on the page body
+
+#### Scenario: Touch target sizing
+- **GIVEN** the tablet layout is active
+- **THEN** all interactive controls (buttons, dropdowns, selects, status pills) MUST meet a minimum 44x44 CSS pixel touch target
+- **AND** focus rings MUST remain visible
+
+### Requirement: REQ-CDV — 07c: Print View
+The dashboard MUST render a clean, printable representation when the user prints the page.
+
+#### Scenario: Print stylesheet applied
+- **GIVEN** a user invokes the browser print dialog (e.g., Ctrl+P)
+- **THEN** `@media print` rules MUST hide navigation, action buttons, save/delete controls, and interactive dropdowns
+- **AND** backgrounds MUST be forced to white and shadows removed
+- **AND** the status timeline MUST render as a textual list (not interactive dots)
+
+#### Scenario: Print header includes identifier and date
+- **GIVEN** the dashboard is printed
+- **THEN** the printed output MUST include a header containing the case identifier and the current print date
+
+### Requirement: REQ-CDV-DASH-01 — Panel Composition
+The dashboard MUST present case data through a fixed set of panel cards.
+
+#### Scenario: CDV-DASH-01-1: Default desktop layout
+- **GIVEN** a user opens a case detail page on a viewport wider than 1200px
+- **THEN** the dashboard MUST render a two-column layout with a primary column (status timeline, activity, documents) and a secondary column (case info, deadline, tasks, participants)
+- **AND** each panel MUST be rendered as a `CnDetailCard` (or equivalent panel component) with a clear title
+
+#### Scenario: CDV-DASH-01-2: Empty panel handling
+- **GIVEN** a case has no tasks, documents, or participants
+- **THEN** each affected panel MUST render an inline empty state with a short Dutch label (e.g., "Geen taken", "Geen documenten", "Geen betrokkenen")
+- **AND** the panel MUST NOT be hidden entirely
+
+### Requirement: REQ-CDV-DASH-02 — Header Actions Visibility
+Page-level header actions MUST adapt to the dashboard's loading and error states.
+
+#### Scenario: CDV-DASH-02-1: Actions during loading
+- **GIVEN** the dashboard is in skeleton loading state
+- **THEN** primary header actions (save, status change, delete) MUST be disabled or hidden
+- **AND** the back link MUST remain available
+
+#### Scenario: CDV-DASH-02-2: Actions in not-found state
+- **GIVEN** the dashboard is in the "Zaak niet gevonden" state
+- **THEN** all header actions related to the case MUST be hidden
+- **AND** only the "Terug naar overzicht" navigation MUST be available
+
+## Dependencies
+- `CaseDetail.vue` (Procest) -- host view that composes the panels
+- `@nextcloud/vue` `NcEmptyContent`, `NcLoadingIcon` -- empty/loading primitives
+- `CnDetailCard` from `@conduction/nextcloud-vue` -- panel container
+- Case data store (object store + filesPlugin) -- supplies case, activity, tasks, documents, participants
diff --git a/openspec/changes/case-dashboard-view/tasks.md b/openspec/changes/archive/2026-05-11-case-dashboard-view/tasks.md
similarity index 100%
rename from openspec/changes/case-dashboard-view/tasks.md
rename to openspec/changes/archive/2026-05-11-case-dashboard-view/tasks.md
diff --git a/openspec/changes/case-definition-portability/.openspec.yaml b/openspec/changes/archive/2026-05-11-case-definition-portability/.openspec.yaml
similarity index 100%
rename from openspec/changes/case-definition-portability/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-case-definition-portability/.openspec.yaml
diff --git a/openspec/changes/case-definition-portability/builds/build.json b/openspec/changes/archive/2026-05-11-case-definition-portability/builds/build.json
similarity index 100%
rename from openspec/changes/case-definition-portability/builds/build.json
rename to openspec/changes/archive/2026-05-11-case-definition-portability/builds/build.json
diff --git a/openspec/changes/case-definition-portability/design.md b/openspec/changes/archive/2026-05-11-case-definition-portability/design.md
similarity index 100%
rename from openspec/changes/case-definition-portability/design.md
rename to openspec/changes/archive/2026-05-11-case-definition-portability/design.md
diff --git a/openspec/changes/case-definition-portability/proposal.md b/openspec/changes/archive/2026-05-11-case-definition-portability/proposal.md
similarity index 100%
rename from openspec/changes/case-definition-portability/proposal.md
rename to openspec/changes/archive/2026-05-11-case-definition-portability/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-case-definition-portability/specs/case-definition-portability/spec.md b/openspec/changes/archive/2026-05-11-case-definition-portability/specs/case-definition-portability/spec.md
new file mode 100644
index 00000000..2f3a578e
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-case-definition-portability/specs/case-definition-portability/spec.md
@@ -0,0 +1,351 @@
+---
+status: implemented
+---
+# case-definition-portability Specification
+
+## Purpose
+Enable export and import of complete case type definitions (zaaktype configurations) as portable archives for DTAP (Development, Test, Acceptance, Production) pipeline deployment and inter-municipality sharing. A case definition package contains the schema, workflow definitions, form configurations, permission rules, and related settings. This eliminates manual recreation of case type configurations across environments and enables a marketplace of reusable zaaktype templates.
+
+## Context
+Mature case management platforms package complete case definitions into portable archives for cross-environment deployment. CaseFabric supports both definition migration and live migration of running cases when definitions change, using event-sourced migration with plan item matching, case file migration, and team migration -- all with full audit trails. Flowable exports CMMN/BPMN/DMN models as versioned deployment archives. Our approach focuses on versioned definition packages that map to OpenRegister schemas and n8n workflows, with explicit conflict resolution and environment parameterization.
+
+## ADDED Requirements
+### Requirement: Case definition export as portable package
+A complete zaaktype configuration MUST be exportable as a single ZIP archive containing all components.
+
+#### Scenario: Export a complete case definition
+- **GIVEN** zaaktype `omgevingsvergunning` is fully configured with:
+ - OpenRegister schemas (case type fields, property definitions, validations)
+ - n8n workflow definitions (intake, assessment, decision flows)
+ - Status types and transitions (ordered statuses with `isFinal`, `notifyInitiator` flags)
+ - Resultaattypen and besluittypen
+ - Role type configuration (roltypen)
+ - Document type templates
+ - ZGW mapping configuration
+- **WHEN** an admin clicks "Exporteren" on the zaaktype in `CaseTypeDetail.vue`
+- **THEN** a ZIP archive MUST be downloaded containing:
+ - `manifest.json` -- version, export date, source environment, Procest version, dependency list
+ - `case-type.json` -- the caseType object with all properties
+ - `schemas/` directory -- OpenRegister schema definitions for all related schemas
+ - `statuses.json` -- ordered status types with transitions
+ - `results.json` -- result type definitions
+ - `decisions.json` -- decision type definitions
+ - `roles.json` -- role type definitions
+ - `documents.json` -- document type definitions
+ - `properties.json` -- property definitions (custom fields)
+ - `workflows/` directory -- n8n workflow JSON exports
+ - `mappings.json` -- ZGW mapping configuration
+ - `permissions.json` -- role-based access configuration
+
+#### Scenario: Export includes version information
+- **GIVEN** a case definition has been exported before as version "1.0.0"
+- **AND** the admin has since added 2 new status types and modified a property definition
+- **WHEN** the definition is exported again
+- **THEN** the manifest MUST show version "1.1.0" (auto-incremented minor version)
+- **AND** the manifest MUST include a `changelog` array listing changes since the previous version
+- **AND** the version MUST follow semantic versioning (major.minor.patch)
+
+#### Scenario: Export captures dependencies
+- **GIVEN** zaaktype `omgevingsvergunning` references a shared `person` schema for zaakbetrokkenen
+- **WHEN** the definition is exported
+- **THEN** the `manifest.json` MUST list `person` as an external dependency with its schema identifier
+- **AND** the `person` schema MUST NOT be included in the archive (it is shared, not owned by this zaaktype)
+- **AND** the manifest MUST specify the minimum compatible version of the `person` schema
+
+#### Scenario: Export via CLI
+- **GIVEN** an admin with shell access to the Nextcloud server
+- **WHEN** they run `docker exec nextcloud php occ procest:export-definition omgevingsvergunning --output /tmp/export.zip`
+- **THEN** the same ZIP archive MUST be produced as from the UI export
+- **AND** the command MUST support `--version` to set a specific version number
+
+#### Scenario: Export sanitizes environment-specific data
+- **GIVEN** an n8n workflow contains a webhook URL `https://test.gemeente.nl/api/intake`
+- **AND** the Procest register has ID `42` in the source environment
+- **WHEN** the definition is exported
+- **THEN** the webhook URL MUST be replaced with `{{BASE_URL}}/api/intake`
+- **AND** OpenRegister IDs MUST be replaced with slugs/identifiers (not numeric IDs)
+- **AND** API keys, credentials, and secrets MUST be stripped from workflow definitions
+
+### Requirement: Case definition import into another environment
+An exported package MUST be importable into a different Nextcloud instance with validation and conflict resolution.
+
+#### Scenario: Import into clean environment
+- **GIVEN** a target environment has OpenRegister and Procest installed but no case types configured
+- **WHEN** an admin uploads the `omgevingsvergunning.zip` package via the import wizard in `CaseTypeAdmin.vue`
+- **THEN** the system MUST create:
+ - The caseType object in OpenRegister
+ - All status types, result types, decision types, role types, document types, and property definitions
+ - n8n workflows via the n8n API (`n8n_create_workflow` MCP tool)
+ - ZGW mapping configuration
+- **AND** the import MUST report success/failure for each component in a results table
+- **AND** all created objects MUST reference each other correctly (no broken links)
+
+#### Scenario: Import with existing dependency resolution
+- **GIVEN** the package depends on a `person` schema that already exists in the target environment
+- **WHEN** importing, the system detects the existing `person` schema by matching on slug/identifier
+- **THEN** it MUST map the reference to the existing schema
+- **AND** it MUST NOT create a duplicate `person` schema
+- **AND** the mapping MUST be shown in the import preview: "person schema -> existing (ID: 78)"
+
+#### Scenario: Import conflict detection and resolution
+- **GIVEN** the target environment already has an `omgevingsvergunning` zaaktype
+- **WHEN** importing a package with the same zaaktype identifier
+- **THEN** the system MUST show a conflict report with a side-by-side diff of differences
+- **AND** offer resolution options per conflicting field:
+ - **Keep existing** -- retain the target's value
+ - **Use imported** -- overwrite with the package's value
+ - **Merge** -- for array fields (e.g., status types), combine both sets
+- **AND** the admin MUST explicitly confirm each resolution before import proceeds
+
+#### Scenario: Import prompts for environment variables
+- **GIVEN** the package contains parameterized values (`{{BASE_URL}}`, `{{SMTP_HOST}}`)
+- **WHEN** the import wizard reaches the environment configuration step
+- **THEN** it MUST prompt the admin to provide values for each parameter
+- **AND** provide sensible defaults where detectable (e.g., current instance URL for `{{BASE_URL}}`)
+- **AND** validate that all parameters are filled before allowing import
+
+#### Scenario: Import rollback on failure
+- **GIVEN** an import is in progress and has created 5 of 8 components
+- **WHEN** the 6th component fails (e.g., n8n workflow creation fails due to missing node type)
+- **THEN** the system MUST roll back all 5 previously created components
+- **AND** report the specific failure with actionable error message
+- **AND** leave the target environment in its pre-import state
+
+### Requirement: Package validation before import
+Before applying an import, the package MUST be validated for completeness, compatibility, and correctness.
+
+#### Scenario: Structural validation
+- **GIVEN** an admin uploads a case definition package
+- **WHEN** the system validates the package structure
+- **THEN** it MUST verify:
+ - `manifest.json` is present and valid JSON
+ - All files referenced in the manifest exist in the archive
+ - JSON files are syntactically valid
+ - Required fields are present in each component file
+
+#### Scenario: Dependency validation
+- **GIVEN** the package references a `subsidy-rules` schema as an external dependency
+- **AND** `subsidy-rules` does not exist in the target environment
+- **THEN** the validation MUST report: "Ontbrekende afhankelijkheid: schema 'subsidy-rules'"
+- **AND** the import MUST be blocked until the dependency is resolved (install the schema or remove the reference)
+
+#### Scenario: Version compatibility validation
+- **GIVEN** the package was exported from Procest v2.5.0
+- **AND** the target environment runs Procest v2.3.0
+- **THEN** the validation MUST check the `minProcestVersion` field in the manifest
+- **AND** if incompatible, report: "Pakket vereist Procest v2.5.0 of hoger. Huidige versie: v2.3.0"
+
+#### Scenario: n8n workflow validation
+- **GIVEN** the package contains 3 n8n workflow JSON files
+- **WHEN** validating
+- **THEN** the system MUST verify each workflow JSON is a valid n8n workflow structure
+- **AND** check that all referenced n8n node types are available in the target n8n instance (via `search_nodes` MCP tool)
+- **AND** report missing node types as warnings (not blocking)
+
+#### Scenario: Validation report presentation
+- **GIVEN** validation completes with 2 errors and 3 warnings
+- **THEN** the import wizard MUST show a validation report with:
+ - Errors (blocking): red, with explanation and suggested fix
+ - Warnings (non-blocking): yellow, with explanation
+ - Passed checks: green, collapsed by default
+- **AND** the "Import" button MUST be disabled until all errors are resolved
+
+### Requirement: Environment-agnostic packaging
+Connection strings, URLs, and environment-specific values MUST be parameterized in exported packages.
+
+#### Scenario: URL parameterization in workflows
+- **GIVEN** an n8n workflow contains webhook URL `https://test.gemeente.nl/api/intake`
+- **WHEN** the workflow is exported
+- **THEN** URLs matching known patterns (the current instance URL) MUST be auto-detected and replaced with `{{BASE_URL}}/api/intake`
+- **AND** the manifest MUST list `BASE_URL` as a required parameter with a description
+
+#### Scenario: Credential stripping
+- **GIVEN** an n8n workflow references a credential named "SMTP Production"
+- **WHEN** the workflow is exported
+- **THEN** the credential reference MUST be preserved as a named placeholder
+- **AND** the actual credential values (passwords, API keys) MUST be stripped
+- **AND** the import wizard MUST prompt the admin to map the credential to an existing credential in the target environment
+
+#### Scenario: OpenRegister ID remapping
+- **GIVEN** the source environment has register ID `42` and schema IDs `101, 102, 103`
+- **WHEN** the definition is exported
+- **THEN** all numeric IDs MUST be replaced with stable identifiers (slugs)
+- **AND** during import, the system MUST resolve slugs to the target environment's IDs
+- **AND** if a slug cannot be resolved, the import MUST report the specific unresolvable reference
+
+#### Scenario: Multi-environment parameter profiles
+- **GIVEN** a municipality has DTAP environments (Development, Test, Acceptance, Production)
+- **WHEN** importing the same package into each environment
+- **THEN** the import wizard MUST support saving parameter profiles (e.g., "Test", "Production")
+- **AND** previously used parameter values MUST be pre-filled when re-importing an updated package version
+
+### Requirement: Selective component export and import
+Admins MUST be able to choose which parts of a definition to export or import.
+
+#### Scenario: Export only schema and statuses
+- **GIVEN** zaaktype `omgevingsvergunning` has schemas, workflows, statuses, results, decisions, and permissions
+- **WHEN** an admin opens the export dialog and deselects workflows, results, decisions, and permissions
+- **THEN** the ZIP MUST contain only `case-type.json`, `schemas/`, `statuses.json`, `properties.json`, and `manifest.json`
+- **AND** the manifest MUST note which components were excluded
+- **AND** excluded components MUST NOT appear as dependencies
+
+#### Scenario: Import only workflows into existing definition
+- **GIVEN** an existing `omgevingsvergunning` zaaktype in the target environment
+- **AND** a package containing updated workflow definitions
+- **WHEN** the admin imports with only "Workflows" selected
+- **THEN** only the n8n workflows MUST be created/updated
+- **AND** the existing statuses, schemas, and other components MUST NOT be modified
+
+#### Scenario: Import individual component from package
+- **GIVEN** a package with 8 components
+- **WHEN** the import wizard shows the component list
+- **THEN** each component MUST have a checkbox (selected by default)
+- **AND** the admin MUST be able to deselect individual components
+- **AND** the system MUST warn if deselecting a component that others depend on
+
+#### Scenario: Export as ZGW Catalogi format
+- **GIVEN** an admin wants to share the zaaktype with a non-Procest system
+- **WHEN** they select "Exporteren als ZGW Catalogi" in the export dialog
+- **THEN** the export MUST produce a JSON file conforming to the ZGW Catalogi API schema (ZaakType, StatusType, ResultaatType, etc.)
+- **AND** this format MUST be importable by any ZGW-compatible system
+
+### Requirement: Definition versioning and change tracking
+Case definitions MUST be versioned with a change history to support controlled DTAP deployment.
+
+#### Scenario: Automatic version tracking
+- **GIVEN** zaaktype `omgevingsvergunning` at version "1.2.0"
+- **WHEN** the admin modifies a status type (changes the name from "Beoordeling" to "Inhoudelijke beoordeling")
+- **AND** saves the zaaktype
+- **THEN** the definition version MUST auto-increment to "1.2.1" (patch for minor change)
+- **AND** the change MUST be recorded: `{"field": "statusType.name", "old": "Beoordeling", "new": "Inhoudelijke beoordeling", "user": "admin", "date": "..."}`
+
+#### Scenario: Version comparison
+- **GIVEN** two exported packages: `omgevingsvergunning-v1.2.0.zip` and `omgevingsvergunning-v1.3.0.zip`
+- **WHEN** an admin uploads both for comparison
+- **THEN** the system MUST show a structured diff:
+ - Added components (green)
+ - Removed components (red)
+ - Modified components (yellow, with field-level diff)
+
+#### Scenario: Version pinning for running cases
+- **GIVEN** 50 active cases using zaaktype `omgevingsvergunning` v1.2.0
+- **WHEN** the admin imports v1.3.0 (which adds a new required status)
+- **THEN** existing running cases MUST continue using v1.2.0 rules
+- **AND** only new cases MUST use v1.3.0
+- **AND** the admin MUST be able to manually migrate individual running cases to v1.3.0
+
+#### Scenario: Version rollback
+- **GIVEN** zaaktype `omgevingsvergunning` was updated from v1.2.0 to v1.3.0
+- **AND** issues are discovered with v1.3.0
+- **WHEN** the admin triggers rollback
+- **THEN** v1.3.0 MUST be deactivated (no new cases can use it)
+- **AND** v1.2.0 MUST be re-activated as the current version
+- **AND** running v1.3.0 cases MUST be flagged for review
+
+#### Scenario: Export version history
+- **GIVEN** zaaktype `omgevingsvergunning` has versions 1.0.0 through 1.5.0
+- **WHEN** the admin views the version history
+- **THEN** all versions MUST be listed with: version number, date, author, and change summary
+- **AND** any historical version MUST be downloadable as a ZIP package
+
+### Requirement: Live case migration between definition versions
+Running cases MUST be migratable to a new definition version without data loss.
+
+#### Scenario: Migrate case to new definition version
+- **GIVEN** case `zaak-1` is running on zaaktype `omgevingsvergunning` v1.2.0
+- **AND** v1.3.0 adds a new required property "milieu_categorie" and renames status "Beoordeling" to "Inhoudelijke beoordeling"
+- **WHEN** the admin triggers migration of `zaak-1` to v1.3.0
+- **THEN** the case's current status MUST be mapped to the new status name
+- **AND** the new required property MUST be added with a null/default value (flagged for case worker to fill)
+- **AND** removed properties from v1.3.0 MUST be archived (preserved but hidden)
+- **AND** the migration MUST be recorded in the case audit trail
+
+#### Scenario: Bulk migration with preview
+- **GIVEN** 50 cases running on v1.2.0
+- **WHEN** the admin triggers bulk migration to v1.3.0
+- **THEN** the system MUST first show a preview: "50 zaken worden gemigreerd. 3 zaken hebben status 'Beoordeling' die wordt hernoemd. 12 zaken missen het nieuwe veld 'milieu_categorie'."
+- **AND** the admin MUST confirm before migration proceeds
+- **AND** migration MUST be executed as a background job with progress tracking
+
+#### Scenario: Migration conflict for removed status
+- **GIVEN** case `zaak-2` has status "Vooronderzoek" which was removed in v1.3.0
+- **WHEN** migration is attempted
+- **THEN** the system MUST flag `zaak-2` as requiring manual intervention
+- **AND** the admin MUST map the removed status to an existing v1.3.0 status before migration can proceed
+
+#### Scenario: Migration preserves task state
+- **GIVEN** case `zaak-1` has 3 active tasks
+- **WHEN** migrated to v1.3.0
+- **THEN** existing tasks MUST be preserved with their current state and assignees
+- **AND** tasks referencing removed properties or statuses MUST be flagged for review
+
+### Requirement: Inter-municipality sharing
+Case definitions MUST be shareable between municipalities via a registry or direct exchange.
+
+#### Scenario: Publish to shared registry
+- **GIVEN** a municipality has a well-tested `woo-verzoek` zaaktype
+- **WHEN** the admin clicks "Publiceren naar bibliotheek" (publish to library)
+- **THEN** the definition package MUST be uploaded to a shared registry (OpenCatalogi or a dedicated Procest template registry)
+- **AND** the listing MUST include: name, description, version, municipality of origin, and screenshot
+
+#### Scenario: Browse and install from registry
+- **GIVEN** the Procest template library shows 15 available zaaktype templates
+- **WHEN** an admin searches for "WOO" and finds the published `woo-verzoek` template
+- **THEN** they MUST be able to preview the template's components (statuses, properties, workflows)
+- **AND** install it into their environment using the standard import flow
+
+#### Scenario: Template rating and feedback
+- **GIVEN** a municipality installed a shared template
+- **THEN** they MUST be able to rate the template (1-5 stars) and leave feedback
+- **AND** the rating MUST be visible to other municipalities browsing the registry
+
+### Requirement: Import/export audit trail
+All import and export operations MUST be logged for compliance and troubleshooting.
+
+#### Scenario: Export audit entry
+- **GIVEN** an admin exports zaaktype `omgevingsvergunning`
+- **THEN** an audit entry MUST be created with: user, timestamp, zaaktype, version, and components included
+
+#### Scenario: Import audit entry
+- **GIVEN** an admin imports a case definition package
+- **THEN** an audit entry MUST record: user, timestamp, package name, version, source environment, components imported, and conflict resolutions applied
+
+#### Scenario: Migration audit entry
+- **GIVEN** 50 cases are migrated from v1.2.0 to v1.3.0
+- **THEN** an audit entry MUST record: user, timestamp, source version, target version, number of cases migrated, number of cases requiring manual intervention, and any errors
+
+## Dependencies
+- OpenRegister (for case type and schema storage, ConfigurationService for import)
+- n8n MCP (for workflow export/import via `n8n_get_workflow`, `n8n_create_workflow`)
+- OpenCatalogi (optional, for shared template registry)
+- ZGW Catalogi API (optional, for interoperable export format)
+- Nextcloud background jobs (for bulk migration processing)
+
+---
+
+### Current Implementation Status
+
+**Not yet implemented.** No export/import functionality for case type definitions exists in the codebase. There are no controllers, services, or UI components for definition portability.
+
+**Foundation available:**
+- `SettingsService::loadConfiguration()` (`lib/Service/SettingsService.php`) imports register configuration from `procest_register.json` via OpenRegister's `ConfigurationService::importFromApp()`. This import/auto-configure pattern serves as a model for case definition import.
+- The `procest_register.json` file (`lib/Settings/procest_register.json`) defines the complete schema structure for all case type entities, providing a reference format for portable definitions.
+- The repair steps `InitializeSettings` (`lib/Repair/InitializeSettings.php`) and `LoadDefaultZgwMappings` (`lib/Repair/LoadDefaultZgwMappings.php`) demonstrate import/initialization patterns.
+- OpenRegister's `ConfigurationService` has version-aware import with force-reimport capability.
+- n8n workflows can be exported/imported via n8n API (n8n MCP tools: `n8n_get_workflow`, `n8n_create_workflow`).
+- `CaseTypeDetail.vue` provides the UI integration point for export/import buttons.
+- `CaseTypeAdmin.vue` provides the list view where import and template library buttons would be added.
+
+**Partial implementations:** None.
+
+### Standards & References
+
+- **DTAP (Development, Test, Acceptance, Production)**: Standard software deployment pipeline that portability supports.
+- **ZGW Catalogi API (VNG)**: Case type definitions (ZaakType, StatusType, ResultaatType, etc.) follow ZGW Catalogi API schemas, which serve as an interoperable export format.
+- **GEMMA**: Dutch municipal architecture standard promoting reusable configurations across municipalities.
+- **CaseFabric Live Migration**: Reference architecture for migrating running cases between definition versions using event-sourced migration with plan item matching.
+- **Flowable Deployment Archives**: Reference for CMMN/BPMN/DMN model versioning and deployment packaging.
+- **OpenRegister Configuration Format**: The existing `procest_register.json` format provides a well-structured configuration exchange format.
+- **Common Ground**: Emphasizes configuration portability across municipalities via standardized APIs.
+- **Semantic Versioning (semver)**: Version numbering standard for definition packages.
+- **CMMN 1.1**: Case definitions map to CasePlanModel; export format should preserve CMMN semantics.
diff --git a/openspec/changes/case-definition-portability/tasks.md b/openspec/changes/archive/2026-05-11-case-definition-portability/tasks.md
similarity index 100%
rename from openspec/changes/case-definition-portability/tasks.md
rename to openspec/changes/archive/2026-05-11-case-definition-portability/tasks.md
diff --git a/openspec/changes/case-management/.openspec.yaml b/openspec/changes/archive/2026-05-11-case-management/.openspec.yaml
similarity index 100%
rename from openspec/changes/case-management/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-case-management/.openspec.yaml
diff --git a/openspec/changes/case-management/builds/build.json b/openspec/changes/archive/2026-05-11-case-management/builds/build.json
similarity index 100%
rename from openspec/changes/case-management/builds/build.json
rename to openspec/changes/archive/2026-05-11-case-management/builds/build.json
diff --git a/openspec/changes/case-management/design.md b/openspec/changes/archive/2026-05-11-case-management/design.md
similarity index 100%
rename from openspec/changes/case-management/design.md
rename to openspec/changes/archive/2026-05-11-case-management/design.md
diff --git a/openspec/changes/case-management/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/case-management/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/case-management/pipeline-logs/build.2.jsonl.gz b/openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.2.jsonl.gz
similarity index 100%
rename from openspec/changes/case-management/pipeline-logs/build.2.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.2.jsonl.gz
diff --git a/openspec/changes/case-management/pipeline-logs/build.3.jsonl.gz b/openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.3.jsonl.gz
similarity index 100%
rename from openspec/changes/case-management/pipeline-logs/build.3.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.3.jsonl.gz
diff --git a/openspec/changes/case-management/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/case-management/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-management/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/case-management/proposal.md b/openspec/changes/archive/2026-05-11-case-management/proposal.md
similarity index 100%
rename from openspec/changes/case-management/proposal.md
rename to openspec/changes/archive/2026-05-11-case-management/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-case-management/specs/case-management/spec.md b/openspec/changes/archive/2026-05-11-case-management/specs/case-management/spec.md
new file mode 100644
index 00000000..5bbe738c
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-case-management/specs/case-management/spec.md
@@ -0,0 +1,144 @@
+---
+status: implemented
+---
+# case-management Specification
+
+## Purpose
+Define the foundational case lifecycle on top of OpenRegister: creating, reading, updating, deleting, listing, searching, validating, and presenting case objects with their custom properties and required documents. This spec documents the MVP surface implemented by this change set; suspension, sub-cases, and confidentiality enforcement remain out of scope.
+
+## Context
+A `case` (zaak) is the primary domain object in Procest. Cases are stored as OpenRegister objects and rendered through `CaseList.vue` and `CaseDetail.vue` using the shared `createObjectStore` pattern. The list view consumes `_filters` and `_search` query parameters, while detail panels (`CustomPropertiesPanel.vue`, `DocumentChecklist.vue`) read property definitions and document type requirements off the linked case type. Validation rules live in `caseValidation.js`.
+
+## ADDED Requirements
+### Requirement: REQ-CM-LIST-01 — Case List Filters
+The case list MUST expose filters for handler, priority, and overdue status.
+
+#### Scenario: CM-LIST-01-1: Filter by handler
+- **GIVEN** the case list contains cases assigned to multiple handlers
+- **WHEN** the user selects a handler from the handler filter dropdown
+- **THEN** the list MUST refresh with `_filters[handler]` applied via the object store
+- **AND** only cases assigned to the selected handler MUST be visible
+
+#### Scenario: CM-LIST-01-2: Filter by priority
+- **WHEN** the user selects a priority value (e.g., "high")
+- **THEN** the list MUST refresh with `_filters[priority]` applied
+- **AND** only cases matching that priority MUST be visible
+
+#### Scenario: CM-LIST-01-3: Filter overdue cases
+- **WHEN** the user activates the "overdue" toggle
+- **THEN** the list MUST be filtered to cases whose deadline is in the past and whose status is not final
+
+#### Scenario: CM-LIST-01-4: Combined filters
+- **WHEN** the user combines case type, status, handler, priority, and overdue filters
+- **THEN** all filters MUST be applied as an AND condition and reflected in the URL query state
+
+### Requirement: REQ-CM-LIST-02 — Case Search
+The case list MUST support keyword search across title, description, and identifier.
+
+#### Scenario: CM-LIST-02-1: Search by keyword
+- **GIVEN** a case "Omgevingsvergunning verbouwing Kerkstraat 12" with identifier "ZAAK-2026-000123"
+- **WHEN** the user types "Kerkstraat" into the search field
+- **THEN** the list MUST refresh with `_search=Kerkstraat`
+- **AND** the matching case MUST appear in the results
+
+#### Scenario: CM-LIST-02-2: Search by identifier
+- **WHEN** the user types a full or partial case identifier
+- **THEN** matching cases MUST appear in the results regardless of title content
+
+#### Scenario: CM-LIST-02-3: Empty search
+- **WHEN** the search field is cleared
+- **THEN** the `_search` parameter MUST be removed and the full filtered list MUST return
+
+### Requirement: REQ-CM-PROP-01 — Custom Properties Panel
+The case detail view MUST render a panel showing the case's custom properties as defined by its case type.
+
+#### Scenario: CM-PROP-01-1: Render configured properties
+- **GIVEN** a case whose case type defines properties `["aanvraagdatum", "locatie", "bouwsom"]`
+- **WHEN** the user opens the case detail view
+- **THEN** the custom properties panel MUST render each property with its label and current value
+- **AND** properties not defined on the case type MUST NOT appear
+
+#### Scenario: CM-PROP-01-2: Edit a property value
+- **WHEN** the user edits a property value through the panel and saves
+- **THEN** the new value MUST be persisted on the case object
+- **AND** the panel MUST reflect the updated value without a full page reload
+
+#### Scenario: CM-PROP-01-3: Property panel with no definitions
+- **GIVEN** a case whose case type defines no custom properties
+- **THEN** the panel MUST render an empty state ("Geen aanvullende kenmerken") and remain hidden from primary actions
+
+### Requirement: REQ-CM-DOC-01 — Required Documents Checklist
+The case detail view MUST render a checklist showing required document types and their completion status.
+
+#### Scenario: CM-DOC-01-1: Render required documents
+- **GIVEN** a case whose case type defines document types with `required=true`
+- **WHEN** the user opens the case detail view
+- **THEN** the checklist MUST list each required document type with a checkmark when at least one matching case document is present
+
+#### Scenario: CM-DOC-01-2: Missing documents flagged
+- **GIVEN** a required document type with no matching case document
+- **THEN** the checklist MUST render that row as incomplete with an explicit "Ontbreekt" label
+
+#### Scenario: CM-DOC-01-3: Optional documents excluded
+- **GIVEN** a case type that defines optional document types in addition to required ones
+- **THEN** only the required types MUST appear in the checklist
+
+### Requirement: REQ-CM-VAL-01 — Strengthened Case Validation
+Case creation and update MUST surface explicit validation errors for case type validity, missing title, and missing case type.
+
+#### Scenario: CM-VAL-01-1: Case type not yet valid
+- **GIVEN** a case type with `validFrom` in the future
+- **WHEN** the user attempts to create a case using it
+- **THEN** `caseValidation.js` MUST return an error identifying the case type and its `validFrom` date
+- **AND** the form MUST display this error inline next to the case type field
+
+#### Scenario: CM-VAL-01-2: Case type expired
+- **GIVEN** a case type with `validTo` in the past
+- **WHEN** the user attempts to create a case using it
+- **THEN** validation MUST fail with an explicit "vervallen" error referencing the expiration date
+
+#### Scenario: CM-VAL-01-3: Missing required fields
+- **GIVEN** a case create form without title or case type
+- **WHEN** the user submits
+- **THEN** each missing required field MUST be highlighted with a Dutch-language error message
+- **AND** the save action MUST be blocked until errors are resolved
+
+### Requirement: REQ-CM-CRUD-01 — Case CRUD Foundation
+The system MUST support create, read, update, and delete operations on case objects through the shared object store.
+
+#### Scenario: CM-CRUD-01-1: Create case
+- **GIVEN** a valid case type and title
+- **WHEN** the user submits the create form
+- **THEN** a new case object MUST be persisted via `createObjectStore('case').save(...)`
+- **AND** the user MUST be redirected to the new case's detail view
+
+#### Scenario: CM-CRUD-01-2: Update case
+- **WHEN** the user edits a case field (e.g., description, priority, handler) and saves
+- **THEN** the update MUST be persisted and the detail view MUST reflect the new value
+
+#### Scenario: CM-CRUD-01-3: Delete case in initial status
+- **GIVEN** a case in its initial status with no dependent links
+- **WHEN** the user confirms deletion
+- **THEN** the case object MUST be deleted via the object store
+- **AND** the user MUST be returned to the case list
+
+### Requirement: REQ-CM-INT-01 — Custom Properties and Document Checklist Integration
+The case detail view MUST integrate the custom properties panel and the document checklist alongside existing panels.
+
+#### Scenario: CM-INT-01-1: Panels visible on detail view
+- **GIVEN** a case with custom properties and required documents
+- **WHEN** the user opens the detail view
+- **THEN** both `CustomPropertiesPanel.vue` and `DocumentChecklist.vue` MUST be visible
+- **AND** they MUST coexist with the status timeline, deadline, activity, and tasks panels without layout overlap
+
+## Non-Requirements
+- Case suspension (out of scope, deferred to V1)
+- Sub-cases / parent-child case relations (deferred to V1)
+- Confidentiality (`vertrouwelijkheidaanduiding`) enforcement at view-time (deferred to V1)
+- Status blocking by missing properties or documents (deferred to V1)
+
+## Dependencies
+- OpenRegister `case` schema and shared `createObjectStore`
+- `CaseList.vue`, `CaseDetail.vue`, `CustomPropertiesPanel.vue`, `DocumentChecklist.vue`
+- `caseValidation.js` validation utility
+- `case-types` capability for property definitions and document type definitions
diff --git a/openspec/changes/case-management/tasks.md b/openspec/changes/archive/2026-05-11-case-management/tasks.md
similarity index 100%
rename from openspec/changes/case-management/tasks.md
rename to openspec/changes/archive/2026-05-11-case-management/tasks.md
diff --git a/openspec/changes/case-sharing-collaboration/.openspec.yaml b/openspec/changes/archive/2026-05-11-case-sharing-collaboration/.openspec.yaml
similarity index 100%
rename from openspec/changes/case-sharing-collaboration/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-case-sharing-collaboration/.openspec.yaml
diff --git a/openspec/changes/case-sharing-collaboration/builds/build.json b/openspec/changes/archive/2026-05-11-case-sharing-collaboration/builds/build.json
similarity index 100%
rename from openspec/changes/case-sharing-collaboration/builds/build.json
rename to openspec/changes/archive/2026-05-11-case-sharing-collaboration/builds/build.json
diff --git a/openspec/changes/case-sharing-collaboration/design.md b/openspec/changes/archive/2026-05-11-case-sharing-collaboration/design.md
similarity index 100%
rename from openspec/changes/case-sharing-collaboration/design.md
rename to openspec/changes/archive/2026-05-11-case-sharing-collaboration/design.md
diff --git a/openspec/changes/case-sharing-collaboration/proposal.md b/openspec/changes/archive/2026-05-11-case-sharing-collaboration/proposal.md
similarity index 100%
rename from openspec/changes/case-sharing-collaboration/proposal.md
rename to openspec/changes/archive/2026-05-11-case-sharing-collaboration/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-case-sharing-collaboration/specs/case-sharing-collaboration/spec.md b/openspec/changes/archive/2026-05-11-case-sharing-collaboration/specs/case-sharing-collaboration/spec.md
new file mode 100644
index 00000000..5c165ce6
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-case-sharing-collaboration/specs/case-sharing-collaboration/spec.md
@@ -0,0 +1,291 @@
+---
+status: implemented
+---
+# case-sharing-collaboration Specification
+
+## Purpose
+Share case access with external parties (ketenpartners) for inter-organizational collaboration on cases. Supports both token-based access for ad-hoc sharing and account-based access for recurring partners, with scoped permissions controlling what shared parties can view and do.
+
+## Context
+Dutch government case processing frequently requires collaboration between organizations: housing corporations reviewing permit applications, police providing input on event permits, healthcare providers contributing to youth care cases. Currently this happens via email with document attachments, losing audit trail and version control. This spec enables structured case sharing with access controls.
+
+Procest already integrates with Nextcloud's sharing infrastructure (`OCP\Share\IManager`) for file sharing and uses OpenRegister RBAC for permission enforcement. The `ZgwAuthMiddleware` demonstrates external API authentication patterns. This spec extends these foundations to enable case-level sharing with granular permission scoping and partner organization management.
+
+## ADDED Requirements
+### Requirement: Share case with external party via secure token link
+The system MUST support sharing a case with an external party using a cryptographically secure, time-limited token URL.
+
+#### Scenario: Create share link with configurable permissions
+- **GIVEN** a case worker on case "ZAAK-2026-001234"
+- **WHEN** they click "Delen" and select "Link delen"
+- **THEN** the system MUST generate a unique, cryptographically secure token URL (min 128 bits entropy)
+- **AND** the case worker MUST be able to set: expiration date, permission level (bekijken / bekijken + reageren / bekijken + bijdragen), and optional password
+- **AND** the share MUST be logged in the case audit trail with: creator, timestamp, permission level, and expiration
+
+#### Scenario: Access shared case via token with view permission
+- **GIVEN** a valid share token for case "ZAAK-2026-001234" with "bekijken" permission
+- **WHEN** the external party opens the token URL in a browser
+- **THEN** they MUST see a public case view with: case title, current status, milestone progress, and selected documents
+- **AND** they MUST NOT see internal notes, assigned case worker names, risk scores, or other restricted fields
+- **AND** they MUST NOT see any other cases or system navigation
+
+#### Scenario: Access shared case via token with comment permission
+- **GIVEN** a valid share token with "bekijken + reageren" permission
+- **WHEN** the external party accesses the case
+- **THEN** they MUST be able to view case details and add comments
+- BUT they MUST NOT be able to upload documents, change case status, or modify any case data
+- **AND** comments MUST be tagged with an external party identifier (name or organization, entered on first access)
+
+#### Scenario: Expired token shows Dutch-language error
+- **GIVEN** a share token that has passed its expiration date
+- **WHEN** the external party attempts to access the case
+- **THEN** the system MUST display "Deze link is verlopen. Neem contact op met de behandelaar." and deny access
+- **AND** the expired access attempt MUST be logged
+
+#### Scenario: Password-protected share link
+- **GIVEN** a share token with password protection enabled
+- **WHEN** the external party opens the token URL
+- **THEN** a password prompt MUST be displayed before granting access
+- **AND** after 5 failed password attempts, the token MUST be temporarily locked for 15 minutes
+
+### Requirement: Share case with registered partner organization
+The system MUST support sharing cases with registered partner organizations (ketenpartners) who have persistent accounts.
+
+#### Scenario: Share with registered ketenpartner
+- **GIVEN** a registered ketenpartner "Woningbouwvereniging Utrecht" with a partner account in the system
+- **WHEN** the case worker shares case "ZAAK-2026-001234" with this partner
+- **THEN** the partner's authorized users MUST see the case in their "Gedeelde zaken" view
+- **AND** the share MUST be scoped to the configured permission level
+- **AND** a notification MUST be sent to the partner organization's primary contact
+
+#### Scenario: Partner organization user management
+- **GIVEN** a registered ketenpartner "Woningbouwvereniging Utrecht"
+- **WHEN** the partner admin manages their organization's users in the partner portal
+- **THEN** they MUST be able to add/remove users who can access shared cases
+- **AND** each user MUST authenticate via their own credentials (Nextcloud account, eHerkenning, or local account)
+- **AND** user changes MUST take effect immediately (no pending approval)
+
+#### Scenario: Partner sees only shared cases
+- **GIVEN** "Woningbouwvereniging Utrecht" has been shared 3 cases from municipality A
+- **WHEN** a partner user logs in
+- **THEN** they MUST see exactly those 3 cases in their "Gedeelde zaken" view
+- **AND** they MUST NOT see any other cases, navigation items, or system configuration
+- **AND** the view MUST show: case title, status, shared date, permission level, and municipality name
+
+#### Scenario: Register new partner organization
+- **GIVEN** a municipality admin wants to add a new ketenpartner
+- **WHEN** they navigate to Settings > Partners and click "Partner toevoegen"
+- **THEN** they MUST provide: organization name, OIN (if applicable), contact email, and default permission level
+- **AND** the system MUST create an OpenRegister object with the partner organization data
+- **AND** a partner admin account MUST be provisioned with a Nextcloud user in the `ketenpartner_{slug}` group
+
+### Requirement: Granular permission levels with field-level control
+Shared access MUST be controllable with granular permission levels and field-level restrictions.
+
+#### Scenario: View-only sharing excludes internal fields
+- **GIVEN** a case shared with permission level "bekijken"
+- **WHEN** the external party views the case
+- **THEN** they MUST see: case title, identifier, current status, milestone progress, and public documents
+- **AND** they MUST NOT see: internal notes (`interneAantekening`), risk scores (`risicoScore`), assigned case worker, cost estimates, or case history details
+
+#### Scenario: View + contribute sharing allows document upload
+- **GIVEN** a case shared with permission level "bekijken + bijdragen"
+- **WHEN** the external party accesses the case
+- **THEN** they MUST be able to upload documents (max 50 MB per file, PDF/DOC/DOCX/JPG/PNG) and add comments
+- **AND** uploaded documents MUST be tagged as "extern aangeleverd" with the uploader's identity
+- **AND** they MUST NOT be able to change case status, zaaktype, assigned worker, or delete existing documents
+
+#### Scenario: Field-level share restrictions via configuration
+- **GIVEN** a share configuration that includes field exclusions: `["interneAantekening", "risicoScore", "kosteninschatting"]`
+- **WHEN** the external party views the case via API or UI
+- **THEN** the excluded fields MUST NOT appear in the case view or API response (not even as empty/null)
+- **AND** the field exclusion MUST be enforced at the API layer before serialization
+
+#### Scenario: Permission level definitions are configurable per tenant
+- **GIVEN** a municipality admin accesses Settings > Deelrechten
+- **WHEN** they define permission levels
+- **THEN** they MUST be able to create custom permission levels with specific field inclusions/exclusions
+- **AND** default permission levels ("bekijken", "bekijken + reageren", "bekijken + bijdragen") MUST be pre-configured
+
+### Requirement: Share lifecycle management
+Case workers MUST be able to view, modify, and revoke active shares on their cases.
+
+#### Scenario: View active shares on case detail
+- **GIVEN** a case with 3 active shares (2 token-based, 1 partner account)
+- **WHEN** the case worker opens the "Delen" tab in the case detail sidebar
+- **THEN** all active shares MUST be listed with: type (link/partner), recipient/label, permission level, creation date, expiration date, last accessed date
+- **AND** each share MUST have an "Intrekken" (revoke) button and an "Aanpassen" (modify) button
+
+#### Scenario: Revoke share immediately blocks access
+- **GIVEN** an active share on a case
+- **WHEN** the case worker clicks "Intrekken" and confirms
+- **THEN** the external party MUST immediately lose access (next page load shows "Toegang ingetrokken")
+- **AND** the revocation MUST be logged in the audit trail with: revoker, timestamp, and share details
+- **AND** any active sessions using the revoked share MUST be invalidated
+
+#### Scenario: Modify share permission level
+- **GIVEN** a token-based share with "bekijken + bijdragen" permission
+- **WHEN** the case worker changes the permission to "bekijken" only
+- **THEN** the external party's next access MUST reflect the reduced permissions
+- **AND** any pending uploads from the external party MUST still be processed (no data loss)
+- **AND** the permission change MUST be logged in the audit trail
+
+#### Scenario: Bulk share management
+- **GIVEN** a case worker handles 20 cases shared with "Politie Utrecht"
+- **WHEN** the case worker navigates to the partner management view
+- **THEN** they MUST see all cases shared with "Politie Utrecht" in a single list
+- **AND** they MUST be able to revoke all shares for that partner at once or modify permissions in bulk
+
+### Requirement: External access activity tracking
+All actions by external parties on shared cases MUST be tracked in the case audit trail.
+
+#### Scenario: External party views case
+- **GIVEN** a shared case accessed by an external party
+- **WHEN** they view the case
+- **THEN** the audit trail MUST record: "Zaak bekeken door extern: Woningbouwvereniging Utrecht (J. de Vries)" with timestamp and IP address
+- **AND** the access MUST be recorded even if the party only views and takes no action
+
+#### Scenario: External party uploads document
+- **GIVEN** a case shared with "bekijken + bijdragen" permission
+- **WHEN** the external party uploads a document "brandveiligheidsadvies.pdf"
+- **THEN** the document MUST be stored in the case's Nextcloud folder under a subfolder "Extern aangeleverd"
+- **AND** the document MUST be tagged with: uploader identity, upload timestamp, and source organization
+- **AND** the audit trail MUST record: "Document geupload door extern: Woningbouwvereniging Utrecht - brandveiligheidsadvies.pdf"
+
+#### Scenario: External party adds comment
+- **GIVEN** a case shared with "bekijken + reageren" permission
+- **WHEN** the external party adds a comment "Brandveiligheid voldoet aan eisen"
+- **THEN** the comment MUST be stored with: author (external party identity), timestamp, and "extern" tag
+- **AND** the comment MUST be visible to case workers in the activity timeline
+- **AND** the case worker MUST receive a notification about the new external comment
+
+### Requirement: Case transfer between organizations
+The system MUST support transferring case ownership from one organization to another.
+
+#### Scenario: Initiate case transfer
+- **GIVEN** case "ZAAK-2026-001234" is owned by municipality A
+- **AND** municipality B's organization is registered as a ketenpartner
+- **WHEN** the case worker initiates a transfer to municipality B
+- **THEN** the system MUST create a transfer request with: source org, target org, case reference, reason, and requested transfer date
+- **AND** the target organization's admin MUST receive a notification to accept or reject the transfer
+
+#### Scenario: Accept case transfer
+- **GIVEN** a pending transfer request for case "ZAAK-2026-001234"
+- **WHEN** the target organization's admin accepts the transfer
+- **THEN** the case MUST be copied to the target organization's register
+- **AND** all documents, status history, and milestone records MUST be included
+- **AND** the source organization MUST retain a read-only archive copy
+- **AND** both organizations' audit trails MUST record the transfer
+
+#### Scenario: Reject case transfer with reason
+- **GIVEN** a pending transfer request
+- **WHEN** the target organization's admin rejects the transfer with reason "Niet bevoegd"
+- **THEN** the source case worker MUST be notified with the rejection reason
+- **AND** the case MUST remain with the source organization unchanged
+
+### Requirement: Public case status page for citizens
+Citizens MUST be able to check their case progress via a public URL without authentication.
+
+#### Scenario: Citizen receives case status link
+- **GIVEN** a citizen submitted a permit application creating case "ZAAK-2026-001234"
+- **WHEN** the case worker sends a status notification
+- **THEN** the notification MUST include a public status URL (e.g., `/publiek/zaak/{token}`)
+- **AND** the token MUST be unique, non-guessable, and linked to the specific case
+
+#### Scenario: Citizen views case progress
+- **GIVEN** a citizen opens the public status URL
+- **THEN** they MUST see: case title, current milestone progress (visual step indicator), current status label, and expected completion date
+- **AND** they MUST NOT see: case worker details, internal notes, documents, or any actionable controls
+- **AND** the page MUST comply with WCAG 2.1 AA and use NL Design System tokens
+
+#### Scenario: Public status page respects case sensitivity
+- **GIVEN** a case is marked as "vertrouwelijk" (confidential)
+- **WHEN** the system generates a status notification
+- **THEN** the public status URL MUST NOT be generated
+- **AND** the citizen MUST be informed via alternative channels (letter, phone)
+
+### Requirement: Notification system for share events
+Case workers and external parties MUST be notified about share-related events.
+
+#### Scenario: Case worker notified of external activity
+- **GIVEN** a case shared with a ketenpartner
+- **WHEN** the ketenpartner uploads a document or adds a comment
+- **THEN** the case worker MUST receive a Nextcloud notification: "Extern document ontvangen op ZAAK-2026-001234 van Woningbouwvereniging Utrecht"
+- **AND** the notification MUST link to the case detail view
+
+#### Scenario: External party notified of case updates
+- **GIVEN** a case shared with a ketenpartner with "bekijken" permission
+- **WHEN** the case status changes
+- **THEN** the ketenpartner's primary contact MUST receive an email notification: "Status update voor ZAAK-2026-001234: Besluit genomen"
+- **AND** the email MUST include a link to the shared case view (not the internal case detail)
+
+#### Scenario: Share expiration reminder
+- **GIVEN** a token-based share expiring in 3 days
+- **WHEN** the daily share maintenance job runs
+- **THEN** the case worker MUST receive a notification: "Deellink voor ZAAK-2026-001234 verloopt over 3 dagen"
+- **AND** they MUST be able to extend the expiration directly from the notification
+
+### Requirement: Data minimization for shared access
+Shared case views MUST apply data minimization principles per AVG/GDPR.
+
+#### Scenario: Personal data excluded from partner view
+- **GIVEN** a case about a building permit that includes the applicant's BSN, address, and phone number
+- **WHEN** shared with a ketenpartner for technical review
+- **THEN** the applicant's BSN MUST be masked (showing only last 4 digits)
+- **AND** personal contact details MUST be excluded unless the permission level explicitly includes them
+- **AND** the data minimization rules MUST be configurable per permission level
+
+#### Scenario: Document metadata stripped for external access
+- **GIVEN** a case document containing metadata (author, revision history, comments)
+- **WHEN** an external party downloads the document via a shared case view
+- **THEN** internal metadata MUST be stripped from the downloaded copy
+- **AND** the original document in Nextcloud MUST remain unchanged
+
+#### Scenario: Audit report for shared personal data
+- **GIVEN** a case with personal data was shared with 3 ketenpartners over 6 months
+- **WHEN** a privacy officer requests a data sharing report
+- **THEN** the system MUST generate: which personal data fields were shared, with whom, when, for how long, and under which legal basis
+
+## Non-Requirements
+- This spec does NOT cover real-time collaborative editing (simultaneous case editing by multiple parties)
+- This spec does NOT cover federated identity management between municipalities
+- This spec does NOT cover automated case routing between organizations based on jurisdiction
+
+## Dependencies
+- OpenRegister RBAC for permission enforcement
+- Nextcloud share infrastructure (`OCP\Share\IManager`) for token generation and expiration management
+- Nextcloud notification system (`OCP\Notification\IManager`) for share event notifications
+- Audit trail system (OpenRegister audit trails plugin) for tracking external access
+- NL Design System tokens for public case status page styling
+- n8n for email notifications to external parties
+- Partner organization registry (new OpenRegister schema: `partnerOrganization`)
+- CaseDetail.vue sidebar for "Delen" tab integration
+
+---
+
+### Current Implementation Status
+
+**Not yet implemented.** No sharing, token-based access, or ketenpartner collaboration functionality exists in the Procest codebase. There are no share-related schemas, controllers, services, or Vue components.
+
+**Foundation available:**
+- Nextcloud's share infrastructure (`OCP\Share\IManager`) provides token-based sharing with expiration, password protection, and permission levels -- could be leveraged for case sharing.
+- OpenRegister RBAC provides the permission enforcement layer.
+- The audit trail plugin in the object store (`auditTrailsPlugin` in `src/store/modules/object.js`) could track external access events.
+- ZGW authentication middleware (`lib/Middleware/ZgwAuthMiddleware.php`) demonstrates external API authentication patterns that could be adapted for partner access.
+- The `CaseDetail.vue` sidebar already supports tabs (via `sidebarProps`) where a "Delen" tab could be added.
+- The `role` schema in OpenRegister could represent external party roles on shared cases.
+
+**Partial implementations:** None.
+
+### Standards & References
+
+- **Nextcloud Sharing API**: Token-based sharing with expiration, passwords, and permission scopes. Procest can extend Nextcloud's `IShare` interface for case-level sharing.
+- **eHerkenning**: Dutch government-to-business authentication standard for partner organization users. Level 3 (substantieel) recommended for case access.
+- **DigiD**: Dutch citizen authentication for citizen-facing case access (out of scope for this spec but relevant for public status page).
+- **AVG/GDPR**: Data sharing with external parties requires purpose limitation, data minimization, and processing agreements. Article 28 (processor agreements) applies to ketenpartner data access.
+- **BIO (Baseline Informatiebeveiliging Overheid)**: Security requirements for government data sharing, including access logging, encryption in transit, and data classification.
+- **Common Ground**: Federated data access patterns for inter-organizational collaboration. The "notificeren" and "autoriseren" components are relevant.
+- **ZGW Autorisaties API (VNG)**: Authorization scopes for external system access to case data. Could model permission levels as ZGW autorisatie objects.
+- **ArkCase**: Uses `AcmParticipant` model for access control on cases -- participants can be internal users, groups, or external contacts. Similar pattern to Procest's ketenpartner concept.
+- **Dimpact ZAC**: Shares cases between groups via group-based assignment. Does not support external organization sharing -- an opportunity for Procest differentiation.
+- **Ketensamenwerking**: Dutch government term for chain collaboration between public organizations. VNG has published guidelines for secure ketendata exchange.
diff --git a/openspec/changes/case-sharing-collaboration/tasks.md b/openspec/changes/archive/2026-05-11-case-sharing-collaboration/tasks.md
similarity index 100%
rename from openspec/changes/case-sharing-collaboration/tasks.md
rename to openspec/changes/archive/2026-05-11-case-sharing-collaboration/tasks.md
diff --git a/openspec/changes/case-types/.openspec.yaml b/openspec/changes/archive/2026-05-11-case-types/.openspec.yaml
similarity index 100%
rename from openspec/changes/case-types/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-case-types/.openspec.yaml
diff --git a/openspec/changes/case-types/builds/build.json b/openspec/changes/archive/2026-05-11-case-types/builds/build.json
similarity index 100%
rename from openspec/changes/case-types/builds/build.json
rename to openspec/changes/archive/2026-05-11-case-types/builds/build.json
diff --git a/openspec/changes/case-types/design.md b/openspec/changes/archive/2026-05-11-case-types/design.md
similarity index 100%
rename from openspec/changes/case-types/design.md
rename to openspec/changes/archive/2026-05-11-case-types/design.md
diff --git a/openspec/changes/case-types/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-case-types/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/case-types/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-types/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/case-types/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-case-types/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/case-types/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-case-types/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/case-types/proposal.md b/openspec/changes/archive/2026-05-11-case-types/proposal.md
similarity index 100%
rename from openspec/changes/case-types/proposal.md
rename to openspec/changes/archive/2026-05-11-case-types/proposal.md
diff --git a/openspec/changes/case-types/reviews/1.json b/openspec/changes/archive/2026-05-11-case-types/reviews/1.json
similarity index 100%
rename from openspec/changes/case-types/reviews/1.json
rename to openspec/changes/archive/2026-05-11-case-types/reviews/1.json
diff --git a/openspec/changes/case-types/reviews/2.json b/openspec/changes/archive/2026-05-11-case-types/reviews/2.json
similarity index 100%
rename from openspec/changes/case-types/reviews/2.json
rename to openspec/changes/archive/2026-05-11-case-types/reviews/2.json
diff --git a/openspec/changes/archive/2026-05-11-case-types/specs/case-types/spec.md b/openspec/changes/archive/2026-05-11-case-types/specs/case-types/spec.md
new file mode 100644
index 00000000..c9a4bb1d
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-case-types/specs/case-types/spec.md
@@ -0,0 +1,124 @@
+---
+status: implemented
+---
+# case-types Specification
+
+## Purpose
+Define the V1 admin surface for configuring case types (zaaktypen) in Procest: result types, role types, property definitions, document types, the tab layout that hosts them, and the publish-time validation that ensures a case type is usable before it goes live. Status type, deadline, and confidentiality configuration are already shipped via the earlier `zaaktype-configuratie` change; this spec layers the remaining V1 tabs on top.
+
+## Context
+Case types drive every downstream capability: statuses, deadlines, custom properties, required documents, roles, and result/outcome handling. The admin UI is hosted by `CaseTypeDetail.vue`, which composes per-concern tab components. This change introduces the missing V1 tabs (`ResultTypesTab.vue`, `RoleTypesTab.vue`, `PropertiesTab.vue`, `DocumentTypesTab.vue`) and strengthens publish-time validation.
+
+## ADDED Requirements
+### Requirement: REQ-CT-RESULT-01 — Result Type Management Tab
+The case type admin UI MUST provide a tab for managing result types with archival rules.
+
+#### Scenario: CT-RESULT-01-1: List result types
+- **GIVEN** a case type with one or more configured result types
+- **WHEN** the admin opens the "Resultaattypen" tab on `CaseTypeDetail.vue`
+- **THEN** each result type MUST be listed with: name, description, archival classification, and retention period
+
+#### Scenario: CT-RESULT-01-2: Create result type
+- **WHEN** the admin clicks "Resultaattype toevoegen" and submits the form with name, description, and archival rule
+- **THEN** a new result type MUST be persisted on the case type
+- **AND** the tab MUST refresh to show the new entry
+
+#### Scenario: CT-RESULT-01-3: Edit and delete result type
+- **WHEN** the admin edits or deletes an existing result type
+- **THEN** the change MUST be persisted via the object store
+- **AND** deletion MUST be blocked with a Dutch-language error if any case currently references the result type
+
+### Requirement: REQ-CT-ROLE-01 — Role Type Management Tab
+The case type admin UI MUST provide a tab for managing role types referencing the generic role registry.
+
+#### Scenario: CT-ROLE-01-1: List role types
+- **WHEN** the admin opens the "Roltypen" tab
+- **THEN** each configured role type MUST be listed with: name, description, generic role (rolomschrijvinggeneriek), and required flag
+
+#### Scenario: CT-ROLE-01-2: Create role type with generic role selector
+- **WHEN** the admin adds a new role type and picks a generic role from the dropdown ("Initiator", "Belanghebbende", "Behandelaar", etc.)
+- **THEN** the role type MUST be persisted with both the local label and the generic role mapping
+
+#### Scenario: CT-ROLE-01-3: Required role validation
+- **GIVEN** a role type marked as required
+- **THEN** cases of this case type MUST surface a warning when the required role is not filled
+- **AND** the role types tab MUST clearly indicate which roles are required
+
+### Requirement: REQ-CT-PROP-01 — Property Definition Management Tab
+The case type admin UI MUST provide a tab for managing property definitions used by the custom properties panel.
+
+#### Scenario: CT-PROP-01-1: List property definitions
+- **WHEN** the admin opens the "Kenmerken" tab
+- **THEN** each property definition MUST be listed with: key, label, type, required flag, and default value
+
+#### Scenario: CT-PROP-01-2: Create property definition
+- **WHEN** the admin adds a new property definition with key, label, type (text/number/date/select), required flag, and optional default
+- **THEN** the definition MUST be persisted on the case type
+- **AND** it MUST become available in the `CustomPropertiesPanel.vue` on cases of this type
+
+#### Scenario: CT-PROP-01-3: Edit and delete property definition
+- **WHEN** the admin edits or deletes a property definition
+- **THEN** the change MUST be persisted
+- **AND** existing case property values MUST remain intact; deletion only removes the definition, not historical data
+
+### Requirement: REQ-CT-DOC-01 — Document Type Management Tab
+The case type admin UI MUST provide a tab for managing document types with a direction (incoming/outgoing) and a required flag.
+
+#### Scenario: CT-DOC-01-1: List document types
+- **WHEN** the admin opens the "Documenttypen" tab
+- **THEN** each document type MUST be listed with: name, description, direction (inkomend/uitgaand/intern), and required flag
+
+#### Scenario: CT-DOC-01-2: Create document type
+- **WHEN** the admin adds a new document type with name, direction, and required flag
+- **THEN** the document type MUST be persisted on the case type
+- **AND** required document types MUST appear in `DocumentChecklist.vue` on cases of this type
+
+#### Scenario: CT-DOC-01-3: Edit and delete document type
+- **WHEN** the admin edits or deletes a document type
+- **THEN** the change MUST be persisted
+- **AND** existing case documents MUST remain intact; deletion only removes the definition
+
+### Requirement: REQ-CT-TABS-01 — V1 Tab Layout
+`CaseTypeDetail.vue` MUST present all V1 admin concerns through a stable, navigable tab layout.
+
+#### Scenario: CT-TABS-01-1: Tab order and labels
+- **WHEN** the admin opens a case type detail view
+- **THEN** the tabs MUST be visible in this order: "Algemeen", "Statussen", "Resultaattypen", "Roltypen", "Kenmerken", "Documenttypen", "Besluiten", "Doorlooptijd"
+- **AND** each tab MUST be labeled in Dutch
+
+#### Scenario: CT-TABS-01-2: Deep link to tab
+- **WHEN** the admin navigates with a URL fragment selecting a tab (e.g., `#documenttypen`)
+- **THEN** the corresponding tab MUST be active on initial render
+
+### Requirement: REQ-CT-PUBLISH-01 — Publish Validation
+The case type publish action MUST be blocked when minimum configuration requirements are not met.
+
+#### Scenario: CT-PUBLISH-01-1: Missing status types
+- **GIVEN** a case type with no status types configured
+- **WHEN** the admin clicks "Publiceren"
+- **THEN** publish MUST be blocked with the error: "Een zaaktype moet minimaal een initiele en een eind-status hebben"
+
+#### Scenario: CT-PUBLISH-01-2: Missing initial or final status
+- **GIVEN** a case type with status types but none marked initial or none marked final
+- **THEN** publish MUST be blocked with a Dutch-language error identifying the missing flag
+
+#### Scenario: CT-PUBLISH-01-3: Validity period
+- **GIVEN** a case type with `validFrom > validTo`
+- **THEN** publish MUST be blocked with the error: "Geldig-vanaf datum moet voor geldig-tot datum liggen"
+
+#### Scenario: CT-PUBLISH-01-4: Successful publish
+- **GIVEN** a case type with at least one initial status, one final status, a valid validity period, and a name
+- **WHEN** the admin clicks "Publiceren"
+- **THEN** the case type's `status` MUST transition from `draft` to `published`
+- **AND** it MUST become selectable on the case creation form
+
+## Non-Requirements
+- Versioning of case types across published versions (deferred)
+- Cross-case-type cloning / templating (deferred)
+- Audit history of case-type configuration changes beyond what OpenRegister provides natively
+
+## Dependencies
+- OpenRegister `caseType` schema and related schemas: `resultType`, `roleType`, `propertyDefinition`, `documentType`
+- `CaseTypeDetail.vue` and the per-concern tab components
+- `case-management` capability (consumes property definitions and document types)
+- Existing `zaaktype-configuratie` change (status types, deadlines, confidentiality)
diff --git a/openspec/changes/case-types/tasks.md b/openspec/changes/archive/2026-05-11-case-types/tasks.md
similarity index 100%
rename from openspec/changes/case-types/tasks.md
rename to openspec/changes/archive/2026-05-11-case-types/tasks.md
diff --git a/openspec/changes/dashboard/builds/build.json b/openspec/changes/archive/2026-05-11-dashboard/builds/build.json
similarity index 100%
rename from openspec/changes/dashboard/builds/build.json
rename to openspec/changes/archive/2026-05-11-dashboard/builds/build.json
diff --git a/openspec/changes/dashboard/design.md b/openspec/changes/archive/2026-05-11-dashboard/design.md
similarity index 100%
rename from openspec/changes/dashboard/design.md
rename to openspec/changes/archive/2026-05-11-dashboard/design.md
diff --git a/openspec/changes/dashboard/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-dashboard/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/dashboard/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-dashboard/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/dashboard/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-dashboard/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/dashboard/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-dashboard/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/dashboard/proposal.md b/openspec/changes/archive/2026-05-11-dashboard/proposal.md
similarity index 100%
rename from openspec/changes/dashboard/proposal.md
rename to openspec/changes/archive/2026-05-11-dashboard/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-dashboard/specs/dashboard/spec.md b/openspec/changes/archive/2026-05-11-dashboard/specs/dashboard/spec.md
new file mode 100644
index 00000000..e914f4b8
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-dashboard/specs/dashboard/spec.md
@@ -0,0 +1,28 @@
+# Delta: dashboard
+
+## ADDED Requirements
+### Requirement: REQ-DASH-003 — Implemented
+The system SHALL satisfy the behaviour described as "REQ-DASH-003 — Implemented".
+
+- Added Cases by Type horizontal bar chart widget to Dashboard.vue
+- Aggregates open cases by case type name, sorted by count descending
+- Click on bar navigates to Cases view filtered by type
+- Uses same CSS bar chart pattern as Cases by Status
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: Application widget registration — Fix
+The system SHALL satisfy the behaviour described as "Application widget registration — Fix".
+
+- Registered CasesOverviewWidget, MyTasksWidget, OverdueCasesWidget in Application.php
+- Fixed CasesOverviewWidget route from `.dashboard.index` to `.dashboard.page`
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
diff --git a/openspec/changes/dashboard/tasks.md b/openspec/changes/archive/2026-05-11-dashboard/tasks.md
similarity index 100%
rename from openspec/changes/dashboard/tasks.md
rename to openspec/changes/archive/2026-05-11-dashboard/tasks.md
diff --git a/openspec/changes/legesberekening/.openspec.yaml b/openspec/changes/archive/2026-05-11-legesberekening/.openspec.yaml
similarity index 100%
rename from openspec/changes/legesberekening/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-legesberekening/.openspec.yaml
diff --git a/openspec/changes/legesberekening/builds/build.json b/openspec/changes/archive/2026-05-11-legesberekening/builds/build.json
similarity index 100%
rename from openspec/changes/legesberekening/builds/build.json
rename to openspec/changes/archive/2026-05-11-legesberekening/builds/build.json
diff --git a/openspec/changes/legesberekening/design.md b/openspec/changes/archive/2026-05-11-legesberekening/design.md
similarity index 100%
rename from openspec/changes/legesberekening/design.md
rename to openspec/changes/archive/2026-05-11-legesberekening/design.md
diff --git a/openspec/changes/legesberekening/proposal.md b/openspec/changes/archive/2026-05-11-legesberekening/proposal.md
similarity index 100%
rename from openspec/changes/legesberekening/proposal.md
rename to openspec/changes/archive/2026-05-11-legesberekening/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-legesberekening/specs/legesberekening/spec.md b/openspec/changes/archive/2026-05-11-legesberekening/specs/legesberekening/spec.md
new file mode 100644
index 00000000..2f8a7b45
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-legesberekening/specs/legesberekening/spec.md
@@ -0,0 +1,524 @@
+---
+status: implemented
+---
+# Legesberekening Specification
+
+## Purpose
+
+Legesberekening is the rules engine that calculates municipal fees (leges) on permit cases. It applies the gemeentelijke legesverordening -- typically based on the VNG modellegesverordening -- to case attributes and produces a calculated amount. The module does NOT handle payment or invoicing; it calculates and exports to the financial system.
+
+**Tender demand**: Found as explicit requirement in 16 VTH tenders. Every VTH tender requires financial system export. Legesberekening is the #1 VTH-specific functional requirement after DSO integration.
+**Standards**: VNG Modellegesverordening, Unie van Waterschappen modelverordening (for waterschappen), StUF-FIN, GEMMA VTH-referentiecomponenten (VTH055-VTH057, VTH103, VTH117, VTH119)
+**Feature tier**: V1 (basic calculation, single verordening, manual export), V2 (multiple verordeningen, automatic DSO import, 4-ogen principe, versioned calculations, financial system connectors)
+
+**Competitive context**: Dimpact ZAC does not include built-in legesberekening -- municipalities typically use their financial system or a separate legesmodule. Flowable can model fee calculations via DMN decision tables, providing a standards-based approach. Procest should implement legesberekening as a PHP calculation service with verordening data stored in OpenRegister, making it fully integrated in the case workflow rather than requiring external tools.
+
+## Calculation Model
+
+### Fee Calculation Types
+
+| Type | Description | Example |
+|------|-------------|---------|
+| Vast bedrag | Fixed amount per application | Sloopmelding: EUR 250 |
+| Percentage | Percentage of bouwkosten | 2.4% of declared construction costs |
+| Staffel | Tiered brackets with different rates per bracket | 0-50K: 3%, 50K-250K: 2.5%, 250K+: 2% |
+| Maximum | Fee capped at a maximum amount | Leges max EUR 50,000 |
+| Minimum | Fee with a minimum floor amount | Leges min EUR 150 |
+| Combinatie | Multiple calculation types combined | Base fee + percentage + surcharge |
+| Staffel vast | Tiered brackets with fixed amounts per bracket | 0-50K: EUR 500, 50K-250K: EUR 1,200 |
+
+### Verordening Structure
+
+```
+Legesverordening (year, valid-from, valid-until)
++-- Titel 1: Algemene dienstverlening
+| +-- Hoofdstuk 1: Burgerzaken
+| | +-- Artikel 1.1.1: Uittreksel GBA -- vast EUR 14,10
+| | +-- Artikel 1.1.2: Rijbewijs -- vast EUR 41,50
+| +-- Hoofdstuk 2: ...
++-- Titel 2: Fysieke leefomgeving (Omgevingswet)
+| +-- Hoofdstuk 1: Omgevingsvergunning bouwactiviteit
+| | +-- Artikel 2.1.1: Bouwkosten t/m EUR 50.000 -- staffel 3,00%
+| | +-- Artikel 2.1.2: Bouwkosten EUR 50.001-250.000 -- staffel 2,50%
+| | +-- Artikel 2.1.3: Bouwkosten > EUR 250.000 -- staffel 2,00%
+| +-- Hoofdstuk 2: ...
++-- Titel 3: Europese dienstenrichtlijn
+```
+
+### OpenRegister Schema Model
+
+```
+legesverordening:
+ title: string # "Legesverordening 2026"
+ year: integer # 2026
+ validFrom: date # 2026-01-01
+ validUntil: date # 2026-12-31
+ status: enum # draft | active | archived
+ municipality: string # gemeente identifier
+
+artikel:
+ verordening: reference # -> legesverordening
+ nummer: string # "2.1.1"
+ titel: string # "Bouwkosten t/m EUR 50.000"
+ hoofdstuk: string # "2.1"
+ type: enum # vast | percentage | staffel | staffel_vast | maximum | minimum
+ tarief: decimal # 3.00 (percentage or fixed amount)
+ grondslag: string # "bouwkosten" (case property to calculate from)
+ rangeMin: decimal # 0 (for staffel)
+ rangeMax: decimal # 50000 (for staffel)
+ maximumBedrag: decimal # null or cap amount
+ minimumBedrag: decimal # null or floor amount
+ caseTypes: array # applicable case type IDs
+
+berekening:
+ case: reference # -> case
+ verordening: reference # -> legesverordening
+ status: enum # concept | ter_accordering | definitief | gecorrigeerd | terugbetaald
+ totalAmount: decimal # 4750.00
+ calculatedBy: string # user UID
+ calculatedAt: datetime # timestamp
+ approvedBy: string # user UID (4-ogen)
+ approvedAt: datetime # timestamp
+ version: integer # 1, 2, 3...
+ reason: string # reason for correction/version
+ lines: array # -> array of berekeningsregel
+
+berekeningsregel:
+ artikel: reference # -> artikel
+ grondslag: string # "bouwkosten"
+ grondslagWaarde: decimal # 180000.00
+ rangeApplied: string # "0 - 50000"
+ tarief: decimal # 3.00
+ bedrag: decimal # 1500.00
+```
+
+## ADDED Requirements
+---
+
+### Requirement: REQ-LEGES-01 — Fee Calculation on Case Attributes
+
+The system MUST calculate leges based on case attributes (bouwkosten, activiteiten, oppervlakte) and the applicable legesverordening.
+
+**Feature tier**: V1
+
+#### Scenario: Staffel (tiered) calculation
+
+- **GIVEN** a case "Omgevingsvergunning Bouw" with bouwkosten = EUR 180,000
+- **AND** legesverordening 2026 with artikel 2.1.1: bouwkosten t/m EUR 50,000 at 3.00% and artikel 2.1.2: EUR 50,001-250,000 at 2.50%
+- **WHEN** legesberekening is triggered via the case dashboard "Leges berekenen" button
+- **THEN** the system MUST calculate: (50,000 x 3.00%) + (130,000 x 2.50%) = EUR 1,500 + EUR 3,250 = EUR 4,750
+- **AND** the calculation MUST be stored as a `berekening` object in OpenRegister with berekeningsregels per artikel
+
+#### Scenario: Fixed amount calculation
+
+- **GIVEN** a case "Sloopmelding" matching artikel 3.2.1: vast bedrag EUR 250
+- **WHEN** legesberekening is triggered
+- **THEN** the system MUST return EUR 250 with reference to artikel 3.2.1
+- **AND** a single berekeningsregel MUST be created with type "vast"
+
+#### Scenario: Corrected construction costs
+
+- **GIVEN** a case with declared bouwkosten = EUR 300,000
+- **AND** the behandelaar corrects bouwkosten to EUR 220,000 (gecorrigeerde bouwsom)
+- **WHEN** legesberekening is recalculated
+- **THEN** the system MUST use the corrected amount EUR 220,000
+- **AND** the calculation history MUST show both the original and corrected calculation as separate versions
+
+#### Scenario: Percentage calculation
+
+- **GIVEN** a case with bouwkosten = EUR 500,000
+- **AND** artikel 2.5.1: percentage 2.4% of bouwkosten
+- **WHEN** legesberekening is triggered
+- **THEN** the system MUST calculate: 500,000 x 2.4% = EUR 12,000
+
+#### Scenario: Maximum cap
+
+- **GIVEN** a case with bouwkosten = EUR 5,000,000
+- **AND** the staffel calculation yields EUR 125,000
+- **AND** the verordening has a maximum cap of EUR 50,000
+- **WHEN** legesberekening is triggered
+- **THEN** the system MUST cap the amount at EUR 50,000
+- **AND** the berekeningsregel MUST show: "Berekend bedrag: EUR 125.000, gemaximeerd op EUR 50.000"
+
+---
+
+### Requirement: REQ-LEGES-02 — Multiple Verordeningen Per Year
+
+The system MUST support multiple legesverordeningen active in the same year (e.g., when rates change mid-year).
+
+**Feature tier**: V2
+
+#### Scenario: Select correct verordening by date
+
+- **GIVEN** legesverordening 2026-A valid from 2026-01-01 to 2026-06-30
+- **AND** legesverordening 2026-B valid from 2026-07-01 to 2026-12-31
+- **AND** a case with startdatum = 2026-08-15
+- **WHEN** legesberekening is triggered
+- **THEN** the system MUST apply verordening 2026-B (active on the case start date)
+
+#### Scenario: No verordening found
+
+- **GIVEN** no active verordening exists for the case's start date
+- **WHEN** legesberekening is triggered
+- **THEN** the system MUST display an error: "Geen actieve legesverordening gevonden voor datum [startdatum]. Neem contact op met de beheerder."
+- **AND** the calculation MUST NOT proceed
+
+#### Scenario: Transitional cases
+
+- **GIVEN** a case started on 2026-06-28 (under verordening 2026-A)
+- **AND** verordening 2026-B takes effect on 2026-07-01
+- **WHEN** legesberekening is triggered on 2026-07-05
+- **THEN** the system MUST use verordening 2026-A (based on case start date, not calculation date)
+
+---
+
+### Requirement: REQ-LEGES-03 — Verrekening, Teruggaaf, and Corrections
+
+The system MUST support deducting previously imposed fees, issuing refunds, and correcting calculations.
+
+**Feature tier**: V1
+
+#### Scenario: Deduct previously imposed leges
+
+- **GIVEN** a case where leges of EUR 4,750 were previously imposed for a provisional permit
+- **AND** the definitive permit has leges of EUR 6,200
+- **WHEN** the behandelaar applies verrekening
+- **THEN** the system MUST calculate the remaining amount: EUR 6,200 - EUR 4,750 = EUR 1,450
+- **AND** the export MUST show the net amount EUR 1,450 with reference to the original assessment
+
+#### Scenario: Refund on withdrawn application (teruggaaf)
+
+- **GIVEN** a case with imposed leges of EUR 4,750
+- **AND** the aanvrager withdraws the application before the besluit
+- **WHEN** the behandelaar initiates teruggaaf
+- **THEN** the system MUST generate a negative amount (EUR -4,750 or partial refund per verordening)
+- **AND** the refund MUST be traceable in the calculation history
+- **AND** the refund percentage MUST be configurable (some verordeningen allow only 75% refund)
+
+#### Scenario: Correction with audit trail
+
+- **GIVEN** a legesberekening with an error (wrong artikel applied)
+- **WHEN** the behandelaar corrects the calculation
+- **THEN** the original calculation MUST be preserved (not overwritten)
+- **AND** the correction MUST be a new version with: reason, corrected by, timestamp
+- **AND** the net difference MUST be exported to the financial system
+
+#### Scenario: Multiple corrections
+
+- **GIVEN** a case with 3 calculation versions: initial (EUR 4,750), correction (EUR 5,200), refund (EUR -2,600)
+- **WHEN** viewing the leges panel on the case dashboard
+- **THEN** all 3 versions MUST be visible with version numbers (v1, v2, v3)
+- **AND** the net result (EUR 2,600) MUST be clearly displayed as the current effective amount
+
+---
+
+### Requirement: REQ-LEGES-04 — 4-Ogen Principe (Four-Eyes Approval)
+
+The system MUST support requiring approval from a second person before a legesberekening becomes definitive.
+
+**Feature tier**: V2
+
+#### Scenario: Require second approval
+
+- **GIVEN** a legesberekening of EUR 12,500 on case "2026-089"
+- **AND** the case type requires 4-ogen principe for leges above EUR 5,000
+- **WHEN** the behandelaar submits the calculation
+- **THEN** the status MUST be set to "Ter accordering"
+- **AND** a task MUST be created for the configured approver (teamleider or financieel medewerker)
+- **AND** the leges MUST NOT be exported until approved
+
+#### Scenario: Approve legesberekening
+
+- **GIVEN** a pending legesberekening "Ter accordering"
+- **WHEN** the approver reviews and approves the calculation
+- **THEN** the status MUST change to "Definitief"
+- **AND** the audit trail MUST record: calculated by, approved by, timestamps
+- **AND** the calculation MUST now be eligible for export
+
+#### Scenario: Reject legesberekening
+
+- **GIVEN** a pending legesberekening "Ter accordering"
+- **WHEN** the approver rejects the calculation with reason "Verkeerd tarief toegepast"
+- **THEN** the status MUST change to "Afgekeurd"
+- **AND** the behandelaar MUST receive a notification with the rejection reason
+- **AND** the behandelaar MUST be able to create a corrected version
+
+#### Scenario: Threshold configuration
+
+- **GIVEN** the beheerder configures 4-ogen thresholds per case type
+- **WHEN** setting threshold to EUR 5,000 for "Omgevingsvergunning"
+- **THEN** calculations below EUR 5,000 MUST proceed directly to "Definitief"
+- **AND** calculations at or above EUR 5,000 MUST require approval
+
+---
+
+### Requirement: REQ-LEGES-05 — Export to Financial System
+
+The system MUST support exporting legesberekeningen to the municipality's financial system. Export is always to an external system -- Procest does NOT handle payment or invoicing.
+
+**Feature tier**: V1
+
+#### Scenario: Generate export file
+
+- **GIVEN** 5 definitieve legesberekeningen ready for export
+- **WHEN** the beheerder triggers a periodic export via the leges admin panel
+- **THEN** the system MUST generate an export containing per record: NAW-gegevens, BSN/KvK debiteur, zaaknummer, leges artikelnummer, omschrijving, bedrag, datum beschikking
+- **AND** the export format MUST be configurable: ASCII (flat file), XML, CSV, or StUF-FIN
+
+#### Scenario: API export to Key2Financien
+
+- **GIVEN** an OpenConnector adapter configured for Key2Financien (Centric)
+- **WHEN** a legesberekening is marked definitief
+- **THEN** the system MUST support automatic push via StUF-FIN or REST API
+- **AND** the financial system reference number MUST be stored back on the berekening object
+- **AND** the export status MUST be tracked: "Te exporteren", "Geexporteerd", "Fout bij export"
+
+#### Scenario: Supported export targets
+
+- The system MUST support export to common financial systems via configurable adapters:
+ - Key2Financien (Centric) -- StUF-FIN or export file
+ - Civision Innen (PinkRoccade) -- Centraal Facturen koppelvlak
+ - iFinancieen (Centric) -- Export/API
+ - Unit4Financials -- ZGW-API
+ - Generic CSV/ASCII for other systems
+
+#### Scenario: Export batch management
+
+- **GIVEN** the beheerder opens the export management screen
+- **THEN** the system MUST show: pending exports count, last export date, export history
+- **AND** each export batch MUST be downloadable as a file
+- **AND** failed exports MUST be retryable individually
+
+---
+
+### Requirement: REQ-LEGES-06 — Verordening Administration
+
+The system MUST support administering legesverordeningen so that fee calculations stay current.
+
+**Feature tier**: V1
+
+#### Scenario: Import verordening from Excel
+
+- **GIVEN** a new legesverordening 2027 prepared in Excel format with columns: artikelnummer, titel, type (vast/percentage/staffel), tarief, grondslag, range_min, range_max, maximum, minimum
+- **WHEN** the beheerder imports the Excel file via the admin panel
+- **THEN** the system MUST parse artikelen, tarieven, grondslagen, and staffels
+- **AND** the verordening MUST be created in draft status for review before activation
+- **AND** import errors MUST be reported per row: "Rij 15: ongeldig tarief '3,00%' -- gebruik decimaal getal (3.00)"
+
+#### Scenario: Test verordening before production
+
+- **GIVEN** a draft legesverordening 2027
+- **WHEN** the beheerder runs a test calculation on a sample case
+- **THEN** the system MUST show the calculated amount using the draft verordening
+- **AND** the test MUST NOT affect the actual case or produce exportable records
+- **AND** the test result MUST show a comparison with the active verordening (if available)
+
+#### Scenario: Activate verordening
+
+- **GIVEN** a draft verordening "2027" that has been reviewed
+- **WHEN** the beheerder clicks "Activeren"
+- **THEN** the verordening status MUST change from "draft" to "active"
+- **AND** any previously active verordening for the same date range MUST be archived
+- **AND** a confirmation dialog MUST warn: "Dit activeert de verordening voor alle nieuwe berekeningen vanaf [validFrom]"
+
+#### Scenario: Manual artikel editing
+
+- **GIVEN** an active verordening
+- **WHEN** the beheerder needs to correct a tarief (e.g., typo: 2.50% should be 2.55%)
+- **THEN** the system MUST allow editing individual artikelen
+- **AND** the edit MUST be logged in the audit trail: "Artikel 2.1.2 tarief gewijzigd van 2.50% naar 2.55% door [beheerder]"
+- **AND** existing calculations MUST NOT be retroactively recalculated (only new calculations use the updated tarief)
+
+---
+
+### Requirement: REQ-LEGES-07 — Calculation Version History
+
+The system MUST maintain a complete version history of all calculations per case, supporting accountantscontrole (audit by external accountant) and rechtmatigheidsverantwoording.
+
+**Feature tier**: V2
+
+#### Scenario: Version history for accountability
+
+- **GIVEN** a case with 3 calculation versions: initial (EUR 4,750), correction (EUR 5,200), refund (EUR -2,600)
+- **WHEN** an accountant reviews the case
+- **THEN** all 3 versions MUST be visible with: timestamp, calculated by, approved by (if 4-ogen), reason for change
+- **AND** the net result (EUR 2,600) MUST be clearly shown
+
+#### Scenario: Export version history as PDF
+
+- **GIVEN** a case with multiple calculation versions
+- **WHEN** the beheerder clicks "Exporteer berekening"
+- **THEN** the system MUST generate a PDF containing: verordening reference, all berekeningsregels per version, totals, audit information
+- **AND** the PDF MUST be suitable for archiving under the Archiefwet
+
+#### Scenario: Immutable history
+
+- **GIVEN** a definitief legesberekening (version 1)
+- **WHEN** a correction is needed
+- **THEN** the system MUST NOT modify version 1
+- **AND** a new version 2 MUST be created with the corrected values
+- **AND** version 1 MUST remain accessible and unmodified
+
+---
+
+### Requirement: REQ-LEGES-08 — Case Dashboard Integration
+
+The legesberekening MUST be accessible from the case dashboard as a dedicated panel.
+
+**Feature tier**: V1
+
+#### Scenario: Leges panel on case dashboard
+
+- **GIVEN** a case of type "Omgevingsvergunning" (which has legesberekening enabled)
+- **WHEN** the behandelaar views the case dashboard
+- **THEN** a "Leges" panel MUST be displayed showing:
+ - Current effective amount (or "Niet berekend" if no calculation exists)
+ - Status (concept/ter_accordering/definitief)
+ - Button "Leges berekenen" (if no calculation) or "Herberekenen" (if calculation exists)
+
+#### Scenario: Calculation breakdown in panel
+
+- **GIVEN** a definitief legesberekening of EUR 4,750
+- **WHEN** the behandelaar expands the leges panel
+- **THEN** the breakdown MUST show per berekeningsregel: artikel nummer, omschrijving, grondslag, tarief, bedrag
+- **AND** the total MUST be shown at the bottom with EUR 4,750
+
+#### Scenario: Trigger calculation from dashboard
+
+- **GIVEN** a case with bouwkosten property filled in
+- **WHEN** the behandelaar clicks "Leges berekenen"
+- **THEN** the system MUST fetch the applicable verordening
+- **AND** calculate the leges using the calculation service
+- **AND** display the result in the leges panel immediately
+- **AND** store the berekening in OpenRegister
+
+#### Scenario: Case type without leges
+
+- **GIVEN** a case type "Klacht" that has no legesberekening configured
+- **WHEN** viewing the case dashboard
+- **THEN** the leges panel MUST NOT be rendered
+
+---
+
+### Requirement: REQ-LEGES-09 — Samenloop (Combined Activities)
+
+The system MUST handle samenloop (combined activities) where a single case has multiple activities each with their own fee calculation, and specific samenloopregels determine the total.
+
+**Feature tier**: V1
+
+#### Scenario: Multiple activities with individual fees
+
+- **GIVEN** a case with activities: "Bouwen" (leges EUR 4,750), "Kappen" (leges EUR 150), "Uitrit" (leges EUR 350)
+- **WHEN** legesberekening is triggered
+- **THEN** the system MUST calculate each activity's leges separately
+- **AND** the total MUST be the sum: EUR 4,750 + EUR 150 + EUR 350 = EUR 5,250
+
+#### Scenario: Samenloop discount
+
+- **GIVEN** a verordening with samenloopkorting: "Bij 3 of meer activiteiten: 10% korting op het totaal"
+- **AND** a case with 3 activities totaling EUR 5,250
+- **WHEN** legesberekening applies samenloopregels
+- **THEN** the discount MUST be calculated: 10% x EUR 5,250 = EUR 525
+- **AND** the final amount MUST be: EUR 5,250 - EUR 525 = EUR 4,725
+
+#### Scenario: Activity-specific rules
+
+- **GIVEN** activity "Bouwen" has a separate staffel calculation based on bouwkosten
+- **AND** activity "Kappen" has a fixed fee of EUR 75 per boom
+- **AND** the case specifies 2 bomen to be kapped
+- **WHEN** calculating the "Kappen" fee
+- **THEN** the system MUST calculate: 2 x EUR 75 = EUR 150
+
+---
+
+### Requirement: REQ-LEGES-10 — Rounding and Precision
+
+The system MUST apply consistent rounding rules to all calculations.
+
+**Feature tier**: V1
+
+#### Scenario: Standard rounding
+
+- **GIVEN** a staffel calculation yielding EUR 4,749.50
+- **WHEN** rounding is applied
+- **THEN** the system MUST round to the nearest whole euro: EUR 4,750 (per VNG modelverordening)
+
+#### Scenario: Intermediate calculations
+
+- **GIVEN** a multi-bracket staffel calculation
+- **WHEN** calculating per bracket
+- **THEN** intermediate results MUST use full precision (no rounding per bracket)
+- **AND** rounding MUST only be applied to the final total
+
+#### Scenario: Minimum fee
+
+- **GIVEN** a percentage calculation yielding EUR 12.50
+- **AND** the minimum fee for this artikel is EUR 150
+- **WHEN** the calculation completes
+- **THEN** the system MUST apply the minimum: EUR 150
+
+## Dependencies
+
+- **Case Management spec** (`../case-management/spec.md`): Leges are calculated on cases.
+- **VTH Module spec** (`../vth-module/spec.md`): Legesberekening is triggered during VTH permit workflow.
+- **Zaak Intake Flow spec** (`../zaak-intake-flow/spec.md`): Bouwkosten imported from DSO intake.
+- **Case Dashboard View spec** (`../case-dashboard-view/spec.md`): Leges panel on case detail.
+- **OpenRegister**: Verordeningen, artikelen, and calculations stored as OpenRegister objects.
+- **OpenConnector**: Financial system export adapters (StUF-FIN, Key2Financien, Civision Innen).
+- **BAG mock register**: Oppervlakte data for fee calculations based on floor area.
+
+### Using Mock Register Data
+
+This spec depends on the **BAG** mock register for oppervlakte (floor area) data used in fee calculations.
+
+**Loading the register:**
+```bash
+# Load BAG register (32 addresses + 21 objects + 21 buildings, register slug: "bag", schemas: "nummeraanduiding", "verblijfsobject", "pand")
+docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/bag_register.json
+```
+
+**Test data for this spec's use cases:**
+- **Oppervlakte for fee calculation**: BAG `verblijfsobject` records include `oppervlakte` (floor area in m2) -- use these values in staffel calculations
+- **Bouwkosten linked to BAG-object**: Link a BAG address to a permit case, then test fee calculation using the declared bouwkosten
+- **Multiple gebruiksdoel types**: BAG records include woonfunctie, kantoorfunctie, winkelfunctie -- test different fee rates per building type
+
+### Current Implementation Status
+
+**Not yet implemented.** No legesberekening-related schemas, controllers, services, or Vue components exist in the Procest codebase. There are no schemas for legesverordening, artikelen, tarieven, or berekeningen in `procest_register.json`.
+
+**Foundation available:**
+- Case properties infrastructure (`case_property_schema` in `SettingsService::SLUG_TO_CONFIG_KEY`) could store calculated leges amounts as case properties (e.g., `bouwkosten`, `legesbedrag`).
+- Property definitions (`property_definition_schema`) could define case type-specific fee-relevant fields.
+- The object store with `auditTrailsPlugin` provides version tracking for calculation history.
+- OpenConnector (external dependency) could host financial system export adapters.
+- The case detail view (`CaseDetail.vue`) could display a "Leges" panel using the existing `CnDetailCard` component pattern.
+- Task management infrastructure could be used for 4-ogen approval tasks.
+- `BrcController.php` demonstrates the ZGW Besluiten API pattern that could be extended for leges export notifications.
+
+**Partial implementations:** None.
+
+### Standards & References
+
+- **VNG Modellegesverordening**: Standard fee ordinance template used by most Dutch municipalities; defines the tariff structure (titels, hoofdstukken, artikelen). Procest follows this structure in the OpenRegister schema model.
+- **StUF-FIN**: XML-based standard for financial system integration in Dutch government.
+- **GEMMA VTH-referentiecomponenten**: VTH055 (Legesberekening), VTH056 (Legesnota), VTH057 (Financiele afhandeling), VTH103, VTH117, VTH119.
+- **Unie van Waterschappen Modelverordening**: Fee ordinance template for waterschappen.
+- **Rechtmatigheidsverantwoording**: Dutch government accountability framework requiring transparent fee calculations with full version history.
+- **Archiefwet**: Calculation records must be retained per archival requirements. PDF export supports this.
+- **Key2Financien / Civision Innen / Unit4Financials / iFinancieen**: Common Dutch municipal financial systems targeted for export.
+- **DMN 1.3**: Flowable uses DMN decision tables for fee calculations; Procest implements equivalent logic in PHP but could expose DMN-compatible rule definitions in the future.
+
+### Specificity Assessment
+
+This is a well-specified domain spec with concrete calculation examples, a clear verordening structure model, and defined OpenRegister schemas.
+
+**Strengths:** Clear calculation type taxonomy (vast, percentage, staffel, maximum, minimum, combinatie, staffel_vast). Concrete arithmetic examples with exact amounts. Verordening hierarchy diagram. Financial system export targets listed. OpenRegister schema model defined. Samenloop rules specified. Rounding rules defined.
+
+**Resolved ambiguities:**
+- Calculation engine is implemented as a PHP service (not n8n workflow), for precision and auditability.
+- Verordeningen use date-based activation with validFrom/validUntil fields.
+- The spec now supports per-article exemptions via the `caseTypes` field on artikelen.
+- Mid-year verordening changes are handled by case start date matching (REQ-LEGES-02c).
+- Calculation precision uses full decimal precision with rounding only on final totals (REQ-LEGES-10).
+- Excel import format is specified with column definitions (REQ-LEGES-06a).
+- 4-ogen threshold is configurable per case type (REQ-LEGES-04d).
diff --git a/openspec/changes/legesberekening/tasks.md b/openspec/changes/archive/2026-05-11-legesberekening/tasks.md
similarity index 100%
rename from openspec/changes/legesberekening/tasks.md
rename to openspec/changes/archive/2026-05-11-legesberekening/tasks.md
diff --git a/openspec/changes/mijn-overheid-integration/.openspec.yaml b/openspec/changes/archive/2026-05-11-mijn-overheid-integration/.openspec.yaml
similarity index 100%
rename from openspec/changes/mijn-overheid-integration/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-mijn-overheid-integration/.openspec.yaml
diff --git a/openspec/changes/mijn-overheid-integration/builds/build.json b/openspec/changes/archive/2026-05-11-mijn-overheid-integration/builds/build.json
similarity index 100%
rename from openspec/changes/mijn-overheid-integration/builds/build.json
rename to openspec/changes/archive/2026-05-11-mijn-overheid-integration/builds/build.json
diff --git a/openspec/changes/mijn-overheid-integration/design.md b/openspec/changes/archive/2026-05-11-mijn-overheid-integration/design.md
similarity index 100%
rename from openspec/changes/mijn-overheid-integration/design.md
rename to openspec/changes/archive/2026-05-11-mijn-overheid-integration/design.md
diff --git a/openspec/changes/mijn-overheid-integration/proposal.md b/openspec/changes/archive/2026-05-11-mijn-overheid-integration/proposal.md
similarity index 100%
rename from openspec/changes/mijn-overheid-integration/proposal.md
rename to openspec/changes/archive/2026-05-11-mijn-overheid-integration/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-mijn-overheid-integration/specs/mijn-overheid-integration/spec.md b/openspec/changes/archive/2026-05-11-mijn-overheid-integration/specs/mijn-overheid-integration/spec.md
new file mode 100644
index 00000000..c0c471ba
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-mijn-overheid-integration/specs/mijn-overheid-integration/spec.md
@@ -0,0 +1,440 @@
+---
+status: implemented
+---
+# mijn-overheid-integration Specification
+
+## Purpose
+Send official government messages to the national Mijn Overheid Berichtenbox from within Procest case context, and provide citizen portal integration for case status tracking. Mijn Overheid is the government-mandated channel for official citizen correspondence. Messages follow strict format requirements and support read tracking. This integration also covers DigiD-authenticated status page access and proactive case status push notifications.
+
+## Context
+Dutch municipalities are increasingly required to send official correspondence (beschikkingen, status updates, decision notifications) through the Mijn Overheid Berichtenbox rather than postal mail. The Wet digitale overheid (Wdo) mandates digital government communication channels. This integration enables Procest case workers to send messages directly from a case, with the message and any attachment stored as case documents for the audit trail. Beyond messaging, Mijn Overheid provides a status page where citizens can track their cases -- Procest pushes status updates to this page via the Zaakstatus API.
+
+## ADDED Requirements
+### Requirement: Send messages to Berichtenbox
+The system MUST support sending messages to a citizen's Mijn Overheid Berichtenbox from within a case context.
+
+#### Scenario: Send a simple text message
+- **GIVEN** a case `zaak-1` with a linked BSN (burgerservicenummer) on a role with type "Initiator"
+- **WHEN** the case worker clicks "Bericht verzenden via Mijn Overheid" in the case detail action menu
+- **THEN** a message composer dialog MUST appear with:
+ - Subject field (pre-filled with case type name if configured)
+ - Body text area (plain text only)
+ - BSN display (read-only, from the case's initiator role)
+ - Bericht type dropdown
+ - Optional PDF attachment upload
+- **AND** upon sending, the system MUST call the Berichtenbox API with the message
+- **AND** the message MUST be stored as a case document in PDF format (generated by Docudesk)
+- **AND** the audit trail in `ActivityTimeline` MUST record: "Bericht verzonden via Mijn Overheid: [subject]"
+
+#### Scenario: Send message with PDF attachment
+- **GIVEN** a case with a linked BSN
+- **WHEN** the case worker attaches a single PDF document to the Berichtenbox message
+- **THEN** the system MUST validate the attachment:
+ - File type MUST be PDF only
+ - File size MUST NOT exceed 10 MB
+ - Only one attachment per message (Mijn Overheid limitation)
+- **AND** the message with attachment MUST be sent via the Berichtenbox API
+- **AND** both the message text and the attachment MUST be stored in the case dossier
+
+#### Scenario: Reject message without BSN
+- **GIVEN** a case `zaak-1` without a linked BSN on any role
+- **WHEN** the case worker opens the "Bericht verzenden via Mijn Overheid" dialog
+- **THEN** the dialog MUST display an error: "BSN is verplicht voor berichten via Mijn Overheid. Koppel eerst een persoon met BSN aan deze zaak."
+- **AND** the send button MUST be disabled
+- **AND** a link to "Persoon toevoegen" MUST be shown
+
+#### Scenario: Send decision notification (beschikking)
+- **GIVEN** a WOO case in stage "Besluit" with an approved decision document
+- **WHEN** the case worker triggers "Beschikking verzenden via Mijn Overheid"
+- **THEN** the system MUST compose a message with:
+ - Subject: "Besluit op uw WOO-verzoek [case identifier]"
+ - Body: standard decision notification text (configurable template)
+ - Attachment: the generated beschikking PDF
+- **AND** the case worker MUST be able to review and edit before sending
+
+#### Scenario: Batch message sending
+- **GIVEN** 15 cases of zaaktype "Vergunning" have reached status "Besluit"
+- **WHEN** the admin triggers "Batch verzenden via Mijn Overheid" from the case list view
+- **THEN** the system MUST:
+ - Validate that all 15 cases have linked BSNs (report any without)
+ - Generate messages using the configured template per zaaktype
+ - Send messages sequentially via the Berichtenbox API (respecting rate limits)
+ - Report results: 13 sent, 2 failed (BSN missing)
+- **AND** each sent message MUST be recorded on its respective case
+
+### Requirement: Bericht type codes for message categorization
+The system MUST support bericht type codes for message routing and categorization within Mijn Overheid.
+
+#### Scenario: Select bericht type on send
+- **GIVEN** a configured set of bericht type codes in the Procest settings
+- **WHEN** the case worker composes a Berichtenbox message
+- **THEN** a bericht type dropdown MUST be displayed with available codes and human-readable labels
+- **AND** the selected type code MUST be included in the API payload
+- **AND** the type code MUST be stored on the sent message record
+
+#### Scenario: Default bericht type per zaaktype
+- **GIVEN** zaaktype `omgevingsvergunning` has a configured default bericht type code "OMG-BESLUIT"
+- **WHEN** the case worker opens the message composer for a case of this type
+- **THEN** the bericht type MUST be pre-selected with "OMG-BESLUIT"
+- **AND** the case worker MUST be able to override the default
+
+#### Scenario: Configure bericht type codes
+- **GIVEN** the admin navigates to zaaktype configuration in `CaseTypeDetail.vue`
+- **WHEN** they open the "Mijn Overheid" configuration tab
+- **THEN** they MUST be able to add bericht type codes with: code, label (Dutch description), and default flag
+- **AND** codes MUST be validated against the Berichtenbox API's accepted codes if the connection is active
+
+#### Scenario: Bericht type required for send
+- **GIVEN** the admin has configured bericht type codes for a zaaktype
+- **WHEN** a case worker attempts to send without selecting a bericht type
+- **THEN** the system MUST display a validation error: "Selecteer een berichttype"
+- **AND** the send button MUST be disabled
+
+### Requirement: Read tracking for sent messages
+The system MUST track whether the citizen has read the message and surface this in the case timeline.
+
+#### Scenario: Poll for read status
+- **GIVEN** a message sent to Berichtenbox with reference ID `msg-abc123`
+- **WHEN** the Nextcloud background job polls the Berichtenbox API for read status
+- **THEN** if the message has been read, the case document MUST be updated with the read timestamp
+- **AND** the case `ActivityTimeline` MUST show: "Bericht gelezen door burger op [datum/tijd]"
+- **AND** the document's metadata MUST include `readAt` timestamp
+
+#### Scenario: Unread message after configurable threshold
+- **GIVEN** a sent message that remains unread for 7 days (default threshold)
+- **WHEN** the polling job detects the threshold is exceeded
+- **THEN** the system MUST create a notification for the case worker: "Bericht '[subject]' is na 7 dagen niet gelezen"
+- **AND** the case timeline MUST show: "Bericht niet gelezen na 7 dagen"
+- **AND** the case worker SHOULD consider alternative contact methods (phone, post)
+
+#### Scenario: Polling frequency configuration
+- **GIVEN** the admin configures Mijn Overheid settings
+- **THEN** they MUST be able to set the polling interval (default: every 6 hours)
+- **AND** the maximum polling duration (default: 30 days after send)
+- **AND** after the maximum duration, polling MUST stop and the message status MUST be set to "onbekend"
+
+#### Scenario: Read status visible in case overview
+- **GIVEN** case `zaak-1` has 3 Berichtenbox messages sent
+- **WHEN** viewing the case's Mijn Overheid section
+- **THEN** each message MUST show its read status: "Gelezen", "Niet gelezen", or "Onbekend"
+- **AND** the most recent message's read status MUST be summarized in the case list view
+
+#### Scenario: Delivery failure handling
+- **GIVEN** the Berichtenbox API returns a delivery failure for a message (e.g., BSN not registered at Mijn Overheid)
+- **THEN** the system MUST mark the message as "Niet bezorgd"
+- **AND** the case timeline MUST show: "Bericht kon niet worden bezorgd: [error reason]"
+- **AND** the case worker MUST be notified to use an alternative communication channel
+
+### Requirement: Message format compliance
+Messages MUST comply with Mijn Overheid Berichtenbox format requirements to ensure delivery.
+
+#### Scenario: Plain text enforcement
+- **GIVEN** a case worker composing a Berichtenbox message
+- **WHEN** they enter the message body
+- **THEN** the editor MUST be plain text only (no HTML, no rich text, no markdown)
+- **AND** pasting formatted text MUST strip all formatting
+- **AND** a character counter MUST show remaining characters (limit: 10,000 characters per Mijn Overheid spec)
+
+#### Scenario: Required fields validation
+- **GIVEN** a message being composed
+- **WHEN** the case worker clicks "Verzenden"
+- **THEN** the system MUST validate:
+ - Subject is present and does not exceed 100 characters
+ - Body is present and does not exceed 10,000 characters
+ - BSN is present and valid (9-digit, passes 11-check)
+ - Bericht type is selected
+- **AND** missing or invalid fields MUST be highlighted with specific validation error messages
+
+#### Scenario: Subject line formatting
+- **GIVEN** a message with subject "Besluit: uw aanvraag omgevingsvergunning OV-2026-001234"
+- **THEN** the subject MUST NOT contain special characters that Mijn Overheid rejects (control characters, HTML tags)
+- **AND** the system MUST sanitize the subject before sending
+
+#### Scenario: Message templates per zaaktype
+- **GIVEN** zaaktype `omgevingsvergunning` has configured message templates
+- **WHEN** the case worker opens the message composer
+- **THEN** they MUST be able to select from templates: "Ontvangstbevestiging", "Besluit vergunning", "Besluit afwijzing"
+- **AND** selecting a template MUST pre-fill the subject and body with merge fields: `{{zaak.identifier}}`, `{{zaak.title}}`, `{{persoon.naam}}`
+- **AND** the case worker MUST be able to edit the pre-filled content before sending
+
+#### Scenario: Message preview before send
+- **GIVEN** a composed message with template merge fields resolved
+- **WHEN** the case worker clicks "Voorbeeld"
+- **THEN** a preview MUST show exactly how the message will appear to the citizen
+- **AND** the preview MUST highlight any potential issues (empty merge fields, near character limit)
+
+### Requirement: Case status push to Mijn Overheid
+The system MUST push case status updates to the Mijn Overheid status page so citizens can track their cases.
+
+#### Scenario: Push status change to Mijn Overheid
+- **GIVEN** a case `zaak-1` with linked BSN changes status from "Intake" to "In behandeling"
+- **AND** Mijn Overheid status push is enabled for this zaaktype
+- **WHEN** the status change is saved
+- **THEN** the system MUST call the Mijn Overheid Zaakstatus API with:
+ - BSN
+ - Zaak identifier
+ - New status name and description
+ - Status change timestamp
+- **AND** the API call result MUST be logged in the case timeline
+
+#### Scenario: Configure status mapping for Mijn Overheid
+- **GIVEN** a zaaktype has 8 internal statuses
+- **WHEN** the admin configures Mijn Overheid status mapping
+- **THEN** they MUST be able to map each internal status to a Mijn Overheid-compatible status label
+- **AND** some internal statuses MAY be configured as "niet publiceren" (e.g., internal review stages)
+- **AND** only mapped statuses MUST trigger a push to Mijn Overheid
+
+#### Scenario: Status push failure retry
+- **GIVEN** a status push to Mijn Overheid fails due to a network error
+- **THEN** the system MUST retry the push up to 3 times with exponential backoff (1min, 5min, 15min)
+- **AND** if all retries fail, the failure MUST be recorded in the case timeline
+- **AND** the case worker MUST be notified: "Statusupdate naar Mijn Overheid mislukt voor zaak [identifier]"
+
+#### Scenario: Initial case registration at Mijn Overheid
+- **GIVEN** a new case is created with a linked BSN
+- **AND** the zaaktype is configured for Mijn Overheid status updates
+- **WHEN** the case is saved
+- **THEN** the system MUST register the case at Mijn Overheid with: zaak identifier, description, expected end date, and initial status
+- **AND** the Mijn Overheid reference ID MUST be stored on the case
+
+#### Scenario: Case completion notification via status page
+- **GIVEN** a case reaches its final status (isFinal: true)
+- **WHEN** the status is pushed to Mijn Overheid
+- **THEN** the status MUST include: "Afgehandeld" with the result type and a link to collect the decision document
+- **AND** if the decision was sent via Berichtenbox, the status MUST reference the message
+
+### Requirement: DigiD-authenticated citizen portal
+Citizens MUST be able to view their case status via a DigiD-authenticated portal page.
+
+#### Scenario: Citizen views case status
+- **GIVEN** a citizen authenticates via DigiD on the municipality's website
+- **WHEN** they navigate to "Mijn zaken" (my cases)
+- **THEN** the system MUST query Procest for all cases linked to the citizen's BSN
+- **AND** display each case with: zaak identifier, type, current status, start date, and expected end date
+
+#### Scenario: Case detail in citizen portal
+- **GIVEN** a citizen clicks on case `zaak-1` in "Mijn zaken"
+- **THEN** they MUST see:
+ - Current status with a visual status timeline
+ - Key dates (submitted, expected completion)
+ - Documents available for download (only "Openbaar" documents and sent messages)
+ - Assigned case worker name (if configured to show)
+ - Contact information for questions
+
+#### Scenario: Portal as API for municipal website
+- **GIVEN** the citizen portal is implemented as an API rather than a standalone UI
+- **THEN** Procest MUST expose a public API endpoint (`/api/public/mijn-zaken`) that accepts a DigiD-authenticated BSN
+- **AND** returns case data in a standardized JSON format
+- **AND** the municipality's website (or Mijn Overheid status page) renders the data
+
+#### Scenario: Portal respects privacy settings
+- **GIVEN** a case has documents with vertrouwelijkheidaanduiding "VERTROUWELIJK" or higher
+- **WHEN** the citizen views the case in the portal
+- **THEN** those documents MUST NOT be visible or downloadable
+- **AND** only documents explicitly marked for citizen access MUST be shown
+
+### Requirement: Admin configuration for Mijn Overheid connection
+Administrators MUST be able to configure the Mijn Overheid API connection with certificate-based authentication.
+
+#### Scenario: Configure API credentials
+- **GIVEN** the Procest admin settings page
+- **WHEN** the admin navigates to the "Mijn Overheid" configuration section
+- **THEN** they MUST be able to enter:
+ - API endpoint URL (SOAP or REST, depending on integration variant)
+ - OIN (Organisatie-identificatienummer) of the municipality
+ - PKIoverheid certificate (upload or file path)
+ - Private key (securely stored in Nextcloud's credential store)
+- **AND** a "Test verbinding" button MUST verify connectivity by calling the Berichtenbox ping endpoint
+- **AND** the connection status MUST be displayed: "Verbonden" (green) or "Niet verbonden" (red with error)
+
+#### Scenario: Configure bericht type codes per zaaktype
+- **GIVEN** the zaaktype configuration screen in `CaseTypeDetail.vue`
+- **WHEN** the admin opens the "Mijn Overheid" tab
+- **THEN** they MUST be able to:
+ - Add bericht type codes with code and label
+ - Set a default bericht type for the zaaktype
+ - Configure message templates with merge fields
+ - Enable/disable status push to Mijn Overheid
+ - Map internal statuses to Mijn Overheid status labels
+
+#### Scenario: Certificate expiration monitoring
+- **GIVEN** a PKIoverheid certificate is configured
+- **THEN** the system MUST check the certificate's expiration date daily
+- **AND** when the certificate expires within 30 days, notify the admin: "PKIoverheid certificaat verloopt op [datum]. Vernieuw het certificaat."
+- **AND** when the certificate has expired, disable Mijn Overheid integration with error: "Certificaat verlopen. Mijn Overheid berichten kunnen niet worden verzonden."
+
+#### Scenario: Test mode (stuuring omgeving)
+- **GIVEN** the admin wants to test the integration without sending to real citizens
+- **WHEN** they enable "Test modus" in the Mijn Overheid configuration
+- **THEN** all API calls MUST be routed to the Mijn Overheid staging environment (stuuromgeving)
+- **AND** sent messages MUST be clearly marked as test messages in the case timeline
+- **AND** a banner MUST appear in the case worker UI: "Mijn Overheid: testmodus actief"
+
+#### Scenario: Connection via OpenConnector
+- **GIVEN** the municipality routes all external API calls through OpenConnector
+- **WHEN** configuring Mijn Overheid
+- **THEN** the admin MUST be able to select an OpenConnector source instead of direct API configuration
+- **AND** the OpenConnector source MUST handle the mTLS certificate, URL routing, and authentication
+- **AND** Procest MUST only send message payloads to OpenConnector
+
+### Requirement: Notification channel selection and fallback
+The system MUST support selecting the appropriate notification channel per case and fall back to alternatives when Mijn Overheid is unavailable.
+
+#### Scenario: Channel selection per citizen
+- **GIVEN** a case with a linked citizen who has opted out of Mijn Overheid (no DigiD account)
+- **WHEN** the case worker attempts to send via Berichtenbox
+- **THEN** the system MUST display: "Deze burger is niet bereikbaar via Mijn Overheid"
+- **AND** suggest alternative channels: email (if available) or postal mail
+- **AND** the case worker MUST be able to send via the alternative channel
+
+#### Scenario: Automatic channel detection
+- **GIVEN** a case with a linked BSN
+- **WHEN** the case worker opens the message composer
+- **THEN** the system MUST check whether the BSN is registered at Mijn Overheid (via the Berichtenbox API)
+- **AND** if registered, default to Berichtenbox
+- **AND** if not registered, display a warning and suggest email
+
+#### Scenario: Multi-channel sending
+- **GIVEN** a municipality wants to send both via Berichtenbox and email for critical decisions
+- **WHEN** the admin configures "dual-channel" for a zaaktype's decision notifications
+- **THEN** the system MUST send the message via both Berichtenbox and email
+- **AND** both sends MUST be recorded in the case timeline
+
+#### Scenario: Postal mail fallback generation
+- **GIVEN** a citizen is not reachable via Mijn Overheid or email
+- **WHEN** the case worker selects "Per post verzenden"
+- **THEN** the system MUST generate a print-ready PDF with the message content and citizen address
+- **AND** store the PDF as a case document
+- **AND** record "Bericht per post verzonden" in the timeline with the print date
+
+### Requirement: Message audit trail and compliance
+All Berichtenbox message interactions MUST be recorded for compliance with Archiefwet and AVG.
+
+#### Scenario: Complete audit record per message
+- **GIVEN** a message is sent via Berichtenbox
+- **THEN** the audit trail MUST record:
+ - Message reference ID (from Berichtenbox API response)
+ - BSN of the recipient
+ - Subject and body (stored as case document)
+ - Bericht type code
+ - Sent timestamp
+ - Sending user
+ - Delivery status (bezorgd/niet bezorgd)
+ - Read status and timestamp (when available)
+
+#### Scenario: Audit entries are immutable
+- **GIVEN** a Berichtenbox message audit entry
+- **THEN** it MUST NOT be editable or deletable by any user (including admin)
+- **AND** it MUST be retained for at least the case's archival retention period per Archiefwet
+
+#### Scenario: Message export for archival
+- **GIVEN** a case is being archived (selectielijst retention period reached)
+- **WHEN** the case is exported for archival
+- **THEN** all Berichtenbox messages MUST be included as PDF documents with full metadata
+- **AND** the export MUST include send/read timestamps and delivery status
+
+#### Scenario: BSN handling compliance
+- **GIVEN** a BSN is used for Berichtenbox message sending
+- **THEN** the BSN MUST be transmitted securely (TLS/mTLS only)
+- **AND** the BSN MUST NOT appear in application logs at INFO level (only DEBUG, and only when explicitly configured)
+- **AND** the BSN display in the UI MUST be partially masked (e.g., "***99*653") except when explicitly viewing the full BSN
+
+#### Scenario: Monthly usage reporting
+- **GIVEN** the admin requests a Mijn Overheid usage report for March 2026
+- **THEN** the system MUST provide:
+ - Total messages sent
+ - Messages per zaaktype
+ - Delivery success rate
+ - Average read time (days between send and read)
+ - Messages still unread after threshold
+ - API errors and retries
+
+### Requirement: Deceased person and special case handling
+The system MUST handle edge cases for citizens who cannot receive Berichtenbox messages.
+
+#### Scenario: Deceased person detection
+- **GIVEN** a case linked to BSN `999999655` (a person marked as deceased in BRP)
+- **WHEN** the case worker attempts to send a Berichtenbox message
+- **THEN** the system MUST check the person's status in BRP (via OpenRegister/BRP mock or Haal Centraal API)
+- **AND** if deceased, display: "Deze persoon is overleden. Bericht kan niet worden verzonden via Mijn Overheid."
+- **AND** suggest contacting the estate executor or next of kin
+
+#### Scenario: Minor (minderjarige) handling
+- **GIVEN** a case linked to a person under 14 years old
+- **WHEN** the case worker attempts to send a Berichtenbox message
+- **THEN** the system MUST display: "Personen onder 14 jaar hebben geen Mijn Overheid account. Bericht wordt verzonden naar wettelijk vertegenwoordiger."
+- **AND** the system MUST look up the legal representative's BSN for message routing
+
+#### Scenario: Organization (niet-natuurlijk persoon) via eHerkenning
+- **GIVEN** a case linked to an organization with KVK number instead of BSN
+- **WHEN** the case worker opens the Berichtenbox message composer
+- **THEN** the system MUST indicate: "Mijn Overheid Berichtenbox is alleen beschikbaar voor burgers (BSN). Gebruik email voor organisaties."
+- **AND** offer the email channel as the default
+
+## Dependencies
+- Mijn Overheid Berichtenbox API (SOAP/REST, mTLS certificate authentication via Logius)
+- Mijn Overheid Zaakstatus API (for case status push)
+- BSN field on case (via linked person record in OpenRegister role)
+- BRP data (via OpenRegister mock register or Haal Centraal BRP API through OpenConnector)
+- Docudesk (for PDF generation of sent messages and decision documents)
+- Nextcloud background jobs (for read status polling and retry logic)
+- OpenConnector (optional, as API proxy for mTLS handling)
+- DigiD (for citizen portal authentication, not directly integrated in Procest but in the municipality's portal)
+- PKIoverheid (certificate infrastructure for mTLS authentication)
+
+---
+
+### Current Implementation Status
+
+**Not yet implemented.** No Mijn Overheid Berichtenbox integration code exists in the Procest codebase. There are no schemas, controllers, services, or Vue components for sending messages to the Berichtenbox or pushing case status to Mijn Overheid.
+
+**Foundation available:**
+- Case detail view (`src/views/cases/CaseDetail.vue`) provides the integration point for a "Bericht verzenden" action in the header actions.
+- Activity timeline (`src/views/cases/components/ActivityTimeline.vue`) could display message sent/read events.
+- Document management (filesPlugin in object store) could store sent messages as case documents.
+- The `dispatch_schema` exists in `SettingsService::SLUG_TO_CONFIG_KEY`, which could be used for message dispatch tracking.
+- `NotificatieService.php` provides notification infrastructure for case worker alerts.
+- Docudesk (external dependency) provides PDF generation for message archival.
+- OpenConnector could host the Berichtenbox API adapter with mTLS handling.
+- BRC controller (`lib/Controller/BrcController.php`) handles decisions, which are the primary content for Berichtenbox messages.
+
+**Partial implementations:** None.
+
+**Mock Registers (dependency):** This spec depends on mock BRP registers being available in OpenRegister for development and testing of BSN-based message sending. These registers are available as JSON files that can be loaded on demand from `openregister/lib/Settings/`.
+
+### Using Mock Register Data
+
+This spec depends on the **BRP** mock register for BSN-based citizen identification and message sending.
+
+**Loading the register:**
+```bash
+# Load BRP register (35 persons, register slug: "brp", schema: "ingeschreven-persoon")
+docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json
+```
+
+**Test data for this spec's use cases:**
+- **Send message to citizen**: BSN `999993653` (Suzanne Moulin) -- test message composition with valid BSN linked to case
+- **Reject without BSN**: Create a case without BSN, verify error "BSN is verplicht voor berichten via Mijn Overheid"
+- **Multiple citizens**: BSN `999990627` (Stephan Janssen), BSN `999992570` (Albert Vogel) -- test message sending to different persons
+- **Deceased person edge case**: BSN `999999655` (Astrid Abels, deceased 2020-06-06) -- test handling of messages to deceased persons
+
+**Querying mock data:**
+```bash
+# Find person by BSN for case linking
+curl "http://localhost:8080/index.php/apps/openregister/api/objects/{brp_register_id}/{person_schema_id}?_search=999993653" -u admin:admin
+```
+
+### Standards & References
+
+- **Mijn Overheid Berichtenbox API**: Government-mandated citizen correspondence channel operated by Logius. Uses SOAP/REST with mTLS certificate authentication.
+- **Mijn Overheid Zaakstatus API**: API for pushing case status updates to the citizen's Mijn Overheid portal.
+- **OIN (Organisatie-identificatienummer)**: Required for government API authentication with Mijn Overheid.
+- **PKIoverheid**: Certificate infrastructure for mTLS authentication.
+- **Digikoppeling**: Dutch government standard for system-to-system communication (required for Berichtenbox).
+- **DigiD**: National authentication service for citizen identity verification.
+- **AVG/GDPR**: BSN processing requires lawful basis and secure handling.
+- **Wet digitale overheid (Wdo)**: Legislation mandating digital government communication channels.
+- **BRP (Basisregistratie Personen)**: BSN lookup for citizen identification and status checking.
+- **Archiefwet**: Archival requirements for government correspondence including Berichtenbox messages.
+- **Haal Centraal BRP API**: Modern REST API for BRP data access (alternative to StUF-BG).
+- **GEMMA**: Mijn Overheid integration is a standard component in the GEMMA reference architecture for citizen communication.
diff --git a/openspec/changes/mijn-overheid-integration/tasks.md b/openspec/changes/archive/2026-05-11-mijn-overheid-integration/tasks.md
similarity index 100%
rename from openspec/changes/mijn-overheid-integration/tasks.md
rename to openspec/changes/archive/2026-05-11-mijn-overheid-integration/tasks.md
diff --git a/openspec/changes/mobiel-inspectie/.openspec.yaml b/openspec/changes/archive/2026-05-11-mobiel-inspectie/.openspec.yaml
similarity index 100%
rename from openspec/changes/mobiel-inspectie/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-mobiel-inspectie/.openspec.yaml
diff --git a/openspec/changes/mobiel-inspectie/builds/build.json b/openspec/changes/archive/2026-05-11-mobiel-inspectie/builds/build.json
similarity index 100%
rename from openspec/changes/mobiel-inspectie/builds/build.json
rename to openspec/changes/archive/2026-05-11-mobiel-inspectie/builds/build.json
diff --git a/openspec/changes/mobiel-inspectie/design.md b/openspec/changes/archive/2026-05-11-mobiel-inspectie/design.md
similarity index 100%
rename from openspec/changes/mobiel-inspectie/design.md
rename to openspec/changes/archive/2026-05-11-mobiel-inspectie/design.md
diff --git a/openspec/changes/mobiel-inspectie/proposal.md b/openspec/changes/archive/2026-05-11-mobiel-inspectie/proposal.md
similarity index 100%
rename from openspec/changes/mobiel-inspectie/proposal.md
rename to openspec/changes/archive/2026-05-11-mobiel-inspectie/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-mobiel-inspectie/specs/mobiel-inspectie/spec.md b/openspec/changes/archive/2026-05-11-mobiel-inspectie/specs/mobiel-inspectie/spec.md
new file mode 100644
index 00000000..269f40c2
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-mobiel-inspectie/specs/mobiel-inspectie/spec.md
@@ -0,0 +1,565 @@
+---
+status: implemented
+---
+# Mobiel Inspectie Specification
+
+## Purpose
+
+Mobiel Inspectie provides field inspectors with a Progressive Web App (PWA) for conducting inspections on location. Inspectors need to complete checklists, take photos, capture GPS coordinates, and add observations -- often in areas with poor or no connectivity. The app syncs data when back online.
+
+**Tender demand**: 16% of tenders (11/69) explicitly require mobile inspection. It is a critical differentiator for VTH tenders -- mobile inspection is the primary tool for field inspectors at omgevingsdiensten.
+**Standards**: PWA (Progressive Web App), Service Workers (offline), Geolocation API, MediaStream API (camera)
+**Feature tier**: V2 (online PWA with photo/GPS), V3 (offline capability, sync queue, field signatures)
+
+## ADDED Requirements
+---
+
+### Requirement: REQ-MOB-01 — PWA Installation and Access
+
+The system MUST provide a Progressive Web App that inspectors can install on mobile devices and access from the browser. The PWA MUST integrate with Nextcloud authentication and launch in standalone mode for a native-like experience.
+
+**Feature tier**: V2
+
+#### Scenario: Install PWA on mobile device
+
+- **GIVEN** an inspector accessing Procest from a mobile browser (Chrome/Safari)
+- **WHEN** the inspector navigates to the inspectie module
+- **THEN** the browser MUST offer "Add to Home Screen" via the PWA manifest
+- **AND** the installed app MUST launch in standalone mode (no browser chrome)
+- **AND** the app MUST use the Nextcloud authentication token for secure access
+
+#### Scenario: Responsive layout for mobile
+
+- **GIVEN** a screen width of 375px (mobile phone)
+- **WHEN** the inspector views a case or checklist
+- **THEN** all UI elements MUST be usable without horizontal scrolling
+- **AND** touch targets MUST be at least 44x44px (WCAG 2.5.5)
+- **AND** the primary actions (complete item, take photo, add note) MUST be accessible within one tap
+
+#### Scenario: PWA manifest configuration
+
+- **GIVEN** the Procest app deployed on a Nextcloud instance
+- **WHEN** the PWA manifest is requested at `/apps/procest/manifest.json`
+- **THEN** it MUST include: `name` ("Procest Inspectie"), `short_name` ("Inspectie"), `start_url` (inspectie module URL), `display` ("standalone"), `orientation` ("portrait"), `theme_color` (Nextcloud primary), `background_color` (white), icons at 192x192 and 512x512
+- **AND** the HTML entry point MUST include ``
+
+#### Scenario: Session persistence on PWA launch
+
+- **GIVEN** an inspector who installed the PWA and previously logged into Nextcloud
+- **WHEN** the inspector opens the PWA from the home screen 48 hours later
+- **THEN** the session MUST still be active if the Nextcloud session token has not expired
+- **AND** if the session has expired, the inspector MUST be redirected to the Nextcloud login page within the standalone PWA window
+
+#### Scenario: Landscape mode for tablets
+
+- **GIVEN** an inspector using a tablet in landscape orientation (1024x768)
+- **WHEN** the inspector views a checklist
+- **THEN** the layout MUST use a split view: checklist items on the left, detail/photo area on the right
+- **AND** the split ratio MUST be adjustable via drag handle
+
+---
+
+### Requirement: REQ-MOB-02 — Inspection Task List
+
+The system MUST display the inspector's assigned inspection tasks for the current day or configurable period, sourced from OpenRegister task objects assigned to the inspector.
+
+**Feature tier**: V2
+
+#### Scenario: Today's inspections
+
+- **GIVEN** inspector "Pieter" with 4 inspections scheduled for today:
+ - 09:00 Bouwtoezicht fase 1 -- Keizersgracht 100
+ - 10:30 Milieucontrole -- Industrieweg 5
+ - 13:00 Bouwtoezicht fase 2 -- Prinsengracht 50
+ - 15:00 Horeca-inspectie -- Leidseplein 12
+- **WHEN** Pieter opens the app
+- **THEN** the task list MUST show all 4 inspections ordered by time
+- **AND** each item MUST show: address, type, case reference, time
+- **AND** each item MUST have a "Navigeer" button that opens the address in the device's map app
+
+#### Scenario: Filter by date range
+
+- **GIVEN** Pieter has 12 inspections scheduled across the current week
+- **WHEN** Pieter selects the date filter and chooses "Deze week"
+- **THEN** the task list MUST show all 12 inspections grouped by day
+- **AND** each day header MUST show the date and number of inspections (e.g., "Maandag 16 maart -- 3 inspecties")
+
+#### Scenario: Empty task list
+
+- **GIVEN** inspector "Lisa" has no inspections scheduled for today
+- **WHEN** Lisa opens the app
+- **THEN** the task list MUST show an empty state message: "Geen inspecties gepland voor vandaag"
+- **AND** a link to "Bekijk komende inspecties" that shows the next 7 days
+
+#### Scenario: Task list data source from OpenRegister
+
+- **GIVEN** inspection tasks are stored as OpenRegister objects in the `procest` register with the `task` schema
+- **AND** task objects have `assignee` matching the current Nextcloud user ID
+- **AND** task objects have `taskType` = "inspection"
+- **WHEN** the app fetches the task list
+- **THEN** it MUST query the OpenRegister API: `GET /api/objects?register={procest}&schema={task}&_filter[assignee]={userId}&_filter[taskType]=inspection&_order[scheduledDate]=asc`
+- **AND** parse the response into the task list view
+
+#### Scenario: Route optimization suggestion
+
+- **GIVEN** inspector "Pieter" has 4 inspections at different addresses
+- **WHEN** Pieter taps "Optimaliseer route"
+- **THEN** the system MUST open the device's map app with all 4 addresses as waypoints
+- **AND** the waypoints SHOULD be ordered to minimize travel distance (using browser geolocation as start point)
+
+---
+
+### Requirement: REQ-MOB-03 — Checklist Completion
+
+The system MUST support completing inspection checklists on the mobile device. Checklists are defined as OpenRegister objects per case type and consist of categorized items with configurable response options.
+
+**Feature tier**: V2
+
+#### Scenario: Complete a checklist item
+
+- **GIVEN** a checklist "Bouwtoezicht fase 1" with 8 items in 3 categories:
+ - Fundering (3 items): Fundering conform tekening, Waterdichting aangebracht, Drainage aanwezig
+ - Constructie (3 items): Wapening conform bestek, Betonkwaliteit gecontroleerd, Dekking wapening voldoende
+ - Veiligheid (2 items): Bouwplaats afgezet, Veiligheidsmaatregelen getroffen
+- **WHEN** the inspector selects item "Fundering conform tekening"
+- **THEN** the inspector MUST be able to select: Conform / Niet-conform / Niet van toepassing
+- **AND** add a free-text toelichting (max 2000 characters)
+- **AND** the progress indicator MUST update: "3/8 items completed"
+
+#### Scenario: Mandatory photo on non-conformity
+
+- **GIVEN** a checklist item configured with "foto verplicht bij niet-conform"
+- **WHEN** the inspector marks the item as "Niet-conform"
+- **THEN** the system MUST require at least one photo before the item can be saved
+- **AND** the photo MUST be captured via the device camera (not from gallery, for evidentiary integrity)
+- **AND** the save button MUST be disabled with tooltip "Voeg minimaal 1 foto toe" until a photo is attached
+
+#### Scenario: Checklist category navigation
+
+- **GIVEN** a checklist with 25 items across 5 categories
+- **WHEN** the inspector views the checklist
+- **THEN** categories MUST be shown as collapsible sections with completion indicators (e.g., "Fundering 2/5")
+- **AND** tapping a category header MUST expand/collapse that section
+- **AND** a sticky header MUST show overall progress: "12/25 items (48%)"
+
+#### Scenario: Checklist item with numeric measurement
+
+- **GIVEN** a checklist item "Geluidsniveau (dB)" configured with response type "numeric" and range 0-120
+- **WHEN** the inspector enters a value of 85
+- **THEN** the value MUST be validated against the configured range
+- **AND** if the value exceeds the threshold (e.g., >80 dB), a warning MUST be displayed: "Waarde overschrijdt norm"
+- **AND** the measurement MUST be stored with the checklist response
+
+#### Scenario: Resume partially completed checklist
+
+- **GIVEN** an inspector who completed 5 of 8 checklist items and closed the app
+- **WHEN** the inspector reopens the app and navigates to the same inspection
+- **THEN** all 5 completed items MUST be shown with their previous responses
+- **AND** the checklist MUST scroll to the first incomplete item
+- **AND** a banner MUST show: "5 van 8 items ingevuld -- ga verder waar u bent gebleven"
+
+---
+
+### Requirement: REQ-MOB-04 — Photo and Document Capture
+
+The system MUST support capturing photos and attaching them to inspection cases and specific checklist items. Photos MUST include metadata (GPS, timestamp) and be stored in Nextcloud Files.
+
+**Feature tier**: V2
+
+#### Scenario: Take inspection photo
+
+- **GIVEN** an active inspection on case "2026-089"
+- **WHEN** the inspector taps "Foto maken"
+- **THEN** the device camera MUST open
+- **AND** after capturing, the photo MUST be linked to: the case, the specific checklist item (if applicable), GPS coordinates, timestamp
+- **AND** the photo MUST be uploaded to Nextcloud Files under the case folder at `/Procest/Zaken/2026-089/Inspecties/{inspectieId}/`
+
+#### Scenario: Annotate photo
+
+- **GIVEN** a captured photo of a building facade
+- **WHEN** the inspector taps "Markeren"
+- **THEN** the inspector MUST be able to draw arrows, circles, or rectangles on the photo to highlight issues
+- **AND** select colors (red, yellow, green) for annotations
+- **AND** the annotated version MUST be saved alongside the original (both stored in Nextcloud Files)
+- **AND** the original MUST be preserved unmodified for evidentiary purposes
+
+#### Scenario: Multiple photos per checklist item
+
+- **GIVEN** a checklist item "Fundering conform tekening" marked as "Niet-conform"
+- **WHEN** the inspector has captured 3 photos for this item
+- **THEN** all 3 photos MUST be displayed as thumbnails below the checklist item
+- **AND** each thumbnail MUST be tappable to view full-size
+- **AND** a delete button (trash icon) MUST allow removing a photo with confirmation dialog
+- **AND** the system MUST enforce a maximum of 10 photos per checklist item
+
+#### Scenario: Photo metadata embedding
+
+- **GIVEN** a photo captured during an inspection at GPS coordinates 52.3676, 4.8913
+- **WHEN** the photo is saved
+- **THEN** the following metadata MUST be stored in the OpenRegister photo object:
+ - `capturedAt` (ISO 8601 timestamp)
+ - `latitude` (52.3676)
+ - `longitude` (4.8913)
+ - `accuracy` (GPS accuracy in meters)
+ - `caseId` (reference to the case)
+ - `checklistItemId` (reference to the specific checklist item, if applicable)
+ - `inspectorId` (Nextcloud user ID of the inspector)
+- **AND** the EXIF data in the JPEG file MUST include GPS coordinates and timestamp
+
+#### Scenario: Camera permission handling
+
+- **GIVEN** an inspector who has not previously granted camera access
+- **WHEN** the inspector taps "Foto maken" for the first time
+- **THEN** the browser MUST prompt for camera permission via the MediaStream API
+- **AND** if permission is denied, the system MUST display: "Camera toegang is vereist voor het maken van inspectie foto's. Ga naar apparaatinstellingen om toegang te verlenen."
+- **AND** a fallback "Upload foto" button MUST allow selecting from the device gallery (with a warning that gallery photos lack evidentiary integrity)
+
+---
+
+### Requirement: REQ-MOB-05 — GPS Location Capture
+
+The system MUST capture GPS coordinates during inspections for geographic verification and audit trail purposes.
+
+**Feature tier**: V2
+
+#### Scenario: Automatic location recording
+
+- **GIVEN** an inspector starting an inspection at Keizersgracht 100
+- **WHEN** the inspection is opened
+- **THEN** the system MUST request GPS permission and record the current coordinates
+- **AND** the coordinates MUST be stored on the inspection record (latitude, longitude, accuracy)
+- **AND** if GPS is unavailable, the system MUST allow manual location confirmation
+
+#### Scenario: Location mismatch warning
+
+- **GIVEN** an inspection planned for Keizersgracht 100 (52.3676, 4.8913)
+- **AND** the inspector's GPS shows coordinates >500m from the planned location
+- **THEN** the system MUST display a warning: "Uw locatie wijkt af van het inspectieadres (afstand: {distance}m)"
+- **AND** the inspector MUST confirm to proceed with reason selection: "Ander adres", "GPS onnauwkeurig", "Inspectie op afstand"
+- **AND** the override reason MUST be stored in the inspection audit trail
+
+#### Scenario: Continuous location tracking during inspection
+
+- **GIVEN** an active inspection in progress
+- **WHEN** the inspector moves between areas on a large site (e.g., construction site)
+- **THEN** the system SHOULD record GPS coordinates at each checklist item completion
+- **AND** a location trail MUST be available in the inspection report
+- **AND** battery consumption MUST be minimized by using the Geolocation API watchPosition with `{ enableHighAccuracy: true, maximumAge: 30000, timeout: 10000 }`
+
+#### Scenario: Indoor location fallback
+
+- **GIVEN** an inspector inside a building where GPS signal is weak (accuracy >100m)
+- **WHEN** the system detects poor GPS accuracy
+- **THEN** it MUST display: "GPS signaal zwak -- locatie is mogelijk onnauwkeurig"
+- **AND** the inspector MUST be able to manually pin the location on a map
+- **AND** the manually pinned location MUST be flagged as `locationSource: "manual"` vs. `locationSource: "gps"`
+
+#### Scenario: Map integration for planned inspections
+
+- **GIVEN** a list of today's inspections with known addresses
+- **WHEN** the inspector taps the map icon in the header
+- **THEN** a map view MUST show all planned inspection locations as pins
+- **AND** completed inspections MUST be shown with a green checkmark pin
+- **AND** the inspector's current location MUST be shown as a blue dot
+- **AND** tapping a pin MUST show the inspection summary with a "Start inspectie" button
+
+---
+
+### Requirement: REQ-MOB-06 — Offline Capability
+
+The system MUST support offline operation for areas with no network connectivity. Data MUST be queued locally and synced when connectivity returns.
+
+**Feature tier**: V3
+
+#### Scenario: Work offline
+
+- **GIVEN** the inspector has loaded today's inspections while online
+- **WHEN** network connectivity is lost
+- **THEN** the inspector MUST still be able to: view assigned inspections, complete checklist items, take photos, add notes
+- **AND** a "Offline" indicator MUST be shown in the app header (orange banner)
+- **AND** all changes MUST be stored in the browser's IndexedDB
+
+#### Scenario: Sync when back online
+
+- **GIVEN** 2 inspections completed offline with 6 photos and 16 checklist responses
+- **WHEN** network connectivity is restored
+- **THEN** the system MUST automatically sync all queued data to the server
+- **AND** a sync progress indicator MUST show: "Synchroniseren: 6/6 foto's, 16/16 checklistitems"
+- **AND** any sync conflicts MUST be flagged for manual resolution (server data wins by default)
+- **AND** the sync status MUST transition through: "Wachten op verbinding" -> "Synchroniseren..." -> "Alles gesynchroniseerd"
+
+#### Scenario: Pre-cache inspection data before going to field
+
+- **GIVEN** an inspector viewing tomorrow's inspection schedule while online
+- **WHEN** the inspector taps "Download voor offline gebruik"
+- **THEN** the Service Worker MUST cache: all inspection task objects, all checklist templates, all case data referenced by inspections, all map tiles for inspection addresses (if supported)
+- **AND** a progress indicator MUST show: "Offline data downloaden: 3/4 inspecties"
+- **AND** the cached data MUST be stored in IndexedDB with a `cachedAt` timestamp
+
+#### Scenario: Offline storage capacity management
+
+- **GIVEN** the browser's IndexedDB storage limit (typically 50-100 MB per origin)
+- **WHEN** cached offline data exceeds 80% of available storage
+- **THEN** the system MUST display: "Opslagruimte bijna vol -- synchroniseer om ruimte vrij te maken"
+- **AND** the system MUST prioritize: pending uploads > current inspection data > historical cache
+- **AND** completed and synced inspections MUST be evictable from the cache
+
+#### Scenario: Conflict resolution after offline sync
+
+- **GIVEN** an inspector completed checklist item "Fundering conform tekening" as "Conform" offline
+- **AND** a colleague updated the same item to "Niet-conform" on the server while the inspector was offline
+- **WHEN** the offline data syncs
+- **THEN** the system MUST detect the conflict and present both versions to the inspector
+- **AND** the conflict dialog MUST show: the offline value, the server value, timestamps, and author for each
+- **AND** the inspector MUST choose: "Mijn versie behouden", "Server versie accepteren", or "Beide bewaren als opmerking"
+
+---
+
+### Requirement: REQ-MOB-07 — Inspection Report Generation
+
+The system MUST support generating a structured inspection report from the completed checklist, including all evidence (photos, measurements, notes).
+
+**Feature tier**: V2
+
+#### Scenario: Generate report on completion
+
+- **GIVEN** an inspection with all checklist items completed
+- **WHEN** the inspector taps "Inspectie afronden"
+- **THEN** the system MUST generate a PDF report via Docudesk containing:
+ - Header: inspection type, case reference, date/time, inspector name
+ - Location: address, GPS coordinates, map thumbnail
+ - Per checklist category: category name, per item: result (Conform/Niet-conform/N.v.t.), toelichting, embedded photos with annotations
+ - Summary: total conform/niet-conform/n.v.t. counts, overall conclusion
+ - Footer: inspector digital signature (if V3), generation timestamp
+- **AND** the report MUST be stored in Nextcloud Files under the case folder
+- **AND** the case status MAY automatically advance (if configured in the case type workflow)
+
+#### Scenario: Draft report preview
+
+- **GIVEN** an inspection with 6 of 8 checklist items completed
+- **WHEN** the inspector taps "Voorvertoning rapport"
+- **THEN** the system MUST generate a draft PDF with a "CONCEPT" watermark
+- **AND** incomplete items MUST be highlighted in yellow
+- **AND** the draft MUST NOT be stored as an official case document
+
+#### Scenario: Report with non-conformities summary
+
+- **GIVEN** an inspection where 3 of 8 items are marked "Niet-conform"
+- **WHEN** the report is generated
+- **THEN** the report MUST include a dedicated "Afwijkingen" section listing all non-conformities
+- **AND** each non-conformity MUST include: item name, toelichting, photos, and a recommended follow-up action field
+- **AND** the report conclusion MUST automatically be set to "Niet goedgekeurd" when any non-conformity exists
+
+---
+
+### Requirement: REQ-MOB-08 — Inspection Schema and Data Model
+
+The system MUST define OpenRegister schemas for inspection entities: inspection, checklist template, checklist item template, checklist response, and inspection photo.
+
+**Feature tier**: V2
+
+#### Scenario: Inspection schema definition
+
+- **GIVEN** the Procest app repair step runs (`InitializeSettings`)
+- **THEN** the following schemas MUST be created in the `procest` register:
+ - `inspection`: `{ caseId, taskId, inspectorId, scheduledDate, startedAt, completedAt, latitude, longitude, accuracy, locationSource, status, overallResult, reportDocumentId }`
+ - `checklistTemplate`: `{ title, caseTypeId, categories, version, isActive }`
+ - `checklistItemTemplate`: `{ checklistTemplateId, category, title, description, responseType (choice|numeric|text), choices (array), requiredPhotoOnFail, numericRange, sortOrder }`
+ - `checklistResponse`: `{ inspectionId, checklistItemTemplateId, response, numericValue, toelichting, respondedAt, respondedBy }`
+ - `inspectionPhoto`: `{ inspectionId, checklistResponseId, fileId, capturedAt, latitude, longitude, accuracy, hasAnnotation, annotatedFileId }`
+
+#### Scenario: Checklist template versioning
+
+- **GIVEN** a checklist template "Bouwtoezicht fase 1" at version 3
+- **AND** 5 inspections have been completed using version 2
+- **WHEN** an admin updates the checklist template
+- **THEN** a new version 4 MUST be created (version 3 becomes immutable)
+- **AND** existing inspections MUST retain their reference to the version used at the time of inspection
+- **AND** new inspections MUST use the latest active version
+
+#### Scenario: Admin creates a checklist template
+
+- **GIVEN** an admin navigating to Procest Settings > Inspectie > Checklists
+- **WHEN** the admin creates a new checklist for case type "Omgevingsvergunning"
+- **THEN** the admin MUST be able to: add categories, add items per category, configure response type per item, set photo requirements, order items via drag-and-drop
+- **AND** the template MUST be stored as an OpenRegister object with the `checklistTemplate` schema
+
+---
+
+### Requirement: REQ-MOB-09 — Digital Signature and Field Sign-off
+
+The system MUST support capturing a digital signature from the inspector (and optionally the site responsible person) to sign off the inspection report.
+
+**Feature tier**: V3
+
+#### Scenario: Inspector signature capture
+
+- **GIVEN** an inspector completing an inspection
+- **WHEN** the inspector taps "Ondertekenen en afronden"
+- **THEN** a signature canvas MUST appear (full-width, 200px height)
+- **AND** the inspector MUST draw their signature using touch
+- **AND** the signature MUST be saved as a PNG image and embedded in the PDF report
+- **AND** the signature MUST include the signer's name and timestamp
+
+#### Scenario: Third-party signature (site responsible)
+
+- **GIVEN** an inspection that requires sign-off from the site responsible person
+- **WHEN** the inspector taps "Handtekening derden"
+- **THEN** the system MUST display: signer name input, signer role input, signature canvas
+- **AND** the third-party signature MUST be stored separately and embedded in the report
+- **AND** the inspector MUST confirm they witnessed the signature
+
+#### Scenario: Refuse to sign
+
+- **GIVEN** a site responsible person who refuses to sign the inspection report
+- **WHEN** the inspector taps "Weigering registreren"
+- **THEN** the system MUST record: refusal reason (free text), timestamp, inspector confirmation
+- **AND** the report MUST include a "Handtekening geweigerd" section with the refusal reason
+
+---
+
+### Requirement: REQ-MOB-10 — Inspection Notifications and Reminders
+
+The system MUST support push notifications to remind inspectors of upcoming inspections and alert them of schedule changes.
+
+**Feature tier**: V2
+
+#### Scenario: Morning briefing notification
+
+- **GIVEN** inspector "Pieter" has 4 inspections scheduled for today
+- **WHEN** the time is 07:00 on the inspection day
+- **THEN** the system MUST send a push notification: "4 inspecties vandaag. Eerste: 09:00 Keizersgracht 100"
+- **AND** tapping the notification MUST open the PWA to today's task list
+
+#### Scenario: Inspection reminder 30 minutes before
+
+- **GIVEN** an inspection scheduled for 10:30 at Industrieweg 5
+- **WHEN** the time is 10:00
+- **THEN** the system MUST send a push notification: "Inspectie over 30 min: Milieucontrole -- Industrieweg 5"
+- **AND** the notification MUST include action buttons: "Navigeer" (opens map), "Details" (opens inspection)
+
+#### Scenario: Schedule change notification
+
+- **GIVEN** an inspection scheduled for 15:00 today
+- **WHEN** the inspection is rescheduled by the coordinator to 09:00 tomorrow
+- **THEN** the inspector MUST receive a push notification: "Inspectie verplaatst: Horeca-inspectie Leidseplein 12 -- nieuw: morgen 09:00"
+- **AND** the task list MUST update to reflect the change (removing from today, adding to tomorrow)
+
+---
+
+### Requirement: REQ-MOB-11 — Inspection History and Follow-up
+
+The system MUST maintain a complete inspection history per location and case, enabling inspectors to view previous findings and track follow-up actions.
+
+**Feature tier**: V2
+
+#### Scenario: View previous inspections at same address
+
+- **GIVEN** the inspector is starting an inspection at Keizersgracht 100
+- **AND** 2 previous inspections were conducted at this address (one 3 months ago, one 6 months ago)
+- **WHEN** the inspector taps "Vorige inspecties"
+- **THEN** the system MUST show a timeline of previous inspections with: date, inspector name, overall result (goedgekeurd/afgekeurd), number of non-conformities
+- **AND** tapping an entry MUST show the full inspection report
+
+#### Scenario: Follow-up action creation from non-conformity
+
+- **GIVEN** an inspection item "Fundering conform tekening" marked as "Niet-conform"
+- **WHEN** the inspector completes the inspection
+- **THEN** the system MUST offer to create a follow-up task: "Herinspectie plannen voor: Fundering conform tekening"
+- **AND** if confirmed, a new task of type "herinspectie" MUST be created in OpenRegister linked to the original inspection and case
+- **AND** the follow-up task MUST include a deadline based on the case type's remediation period
+
+#### Scenario: Compare current vs. previous inspection
+
+- **GIVEN** a follow-up inspection at the same address
+- **WHEN** the inspector views a checklist item that was previously "Niet-conform"
+- **THEN** the system MUST highlight the item with: "Vorige inspectie: Niet-conform (12-01-2026)"
+- **AND** the previous toelichting and photos MUST be viewable inline for comparison
+
+---
+
+### Requirement: REQ-MOB-12 — Accessibility and Usability
+
+The mobile inspection app MUST be accessible and usable in field conditions, including bright sunlight, wet hands, and gloved operation.
+
+**Feature tier**: V2
+
+#### Scenario: High contrast mode for outdoor use
+
+- **GIVEN** an inspector using the app in bright sunlight
+- **WHEN** the inspector enables high-contrast mode (via toggle in the header)
+- **THEN** all text MUST meet WCAG AAA contrast ratio (7:1)
+- **AND** buttons MUST use solid dark backgrounds with white text
+- **AND** the status bar indicators (Conform/Niet-conform) MUST use distinct shapes in addition to color (checkmark/cross) for colorblind accessibility
+
+#### Scenario: Large touch targets for gloved use
+
+- **GIVEN** an inspector wearing safety gloves on a construction site
+- **WHEN** interacting with checklist items
+- **THEN** all interactive elements MUST have a minimum touch target of 48x48px (above WCAG 2.5.5 minimum of 44x44px)
+- **AND** the Conform/Niet-conform/N.v.t. buttons MUST be full-width with 56px height
+- **AND** swipe gestures MUST be supported: swipe right for Conform, swipe left for Niet-conform
+
+#### Scenario: Voice notes as alternative to typing
+
+- **GIVEN** an inspector who needs to add a toelichting but cannot easily type (wet hands, gloves)
+- **WHEN** the inspector taps the microphone icon on the toelichting field
+- **THEN** the system MUST record audio via the MediaStream API
+- **AND** the audio file MUST be attached to the checklist response as evidence
+- **AND** optionally, the system MAY transcribe the audio to text using browser speech recognition API
+
+---
+
+## Dependencies
+
+- **VTH Module spec** (`../vth-module/spec.md`): Inspection checklists are defined per VTH case type.
+- **Case Management spec** (`../case-management/spec.md`): Inspections are tasks/activities within cases.
+- **Task Management spec** (`../task-management/spec.md`): Inspection tasks appear in the inspector's task list.
+- **Docudesk**: PDF report generation from checklist data.
+- **Nextcloud Files**: Photo storage under case folder structure.
+- **OpenRegister**: All inspection data stored as objects (inspection, checklist, response, photo schemas).
+- **Nextcloud Push Notifications** (`OCP\Notification\IManager`): For inspection reminders and schedule changes.
+
+### Current Implementation Status
+
+**Not yet implemented.** No mobile inspection, PWA, checklist, or field inspection code exists in the Procest codebase. There are no inspection schemas, no PWA manifest, no service worker, and no mobile-specific Vue components.
+
+**Foundation available:**
+- Task management infrastructure (`src/views/tasks/TaskList.vue`, `src/views/tasks/TaskDetail.vue`) provides the task list model that inspection assignments could use.
+- File upload support via `filesPlugin` in the object store for photo attachment.
+- The `CnDetailPage` component used in case/task detail views provides sidebar and responsive layout foundations.
+- Nextcloud Files integration for document storage under case folders.
+- Docudesk (external dependency) for PDF report generation from checklist data.
+- `MetricsController` and `HealthController` demonstrate the pattern for new API endpoints.
+
+**Partial implementations:** None.
+
+### Standards & References
+
+- **PWA (Progressive Web App)**: W3C standard for installable web apps with offline capability.
+- **Service Workers**: Browser API for offline caching and background sync (required for V3 offline capability).
+- **IndexedDB**: Browser storage API for offline data persistence.
+- **Geolocation API**: W3C standard for GPS coordinate capture.
+- **MediaStream API (getUserMedia)**: Browser API for camera and microphone access.
+- **WCAG 2.5.5**: Touch target size minimum 44x44px for mobile accessibility.
+- **WCAG 2.1 Level AA**: Overall accessibility compliance target.
+- **GEMMA VTH-referentiecomponenten**: Mobile inspection is part of the VTH reference architecture.
+- **Omgevingswet**: Environmental and planning law requiring field inspections for permit compliance.
+- **BIO**: Security requirements for mobile device data handling (device encryption, secure data transmission).
+- **Web Push API**: W3C standard for push notifications to PWA.
+- **Canvas API**: Browser API for photo annotation drawing features.
+- **SpeechRecognition API**: Browser API for voice-to-text transcription.
+
+### Specificity Assessment
+
+This spec defines 12 requirements with 3-5 scenarios each, covering the full mobile inspection lifecycle from PWA installation through report generation. The V2/V3 tier split is maintained.
+
+**Competitive context:** Dimpact ZAC and Flowable do not offer native mobile inspection PWAs. ZAC relies on third-party mobile inspection tools. Procest's built-in mobile inspection is a significant differentiator for VTH tenders.
+
+**Open questions:**
+1. Should the mobile inspection be a separate Vue app (PWA entry point) or part of the main Procest app with responsive layout?
+2. How are checklists defined -- as OpenRegister schemas, JSON templates, or n8n workflow definitions?
+3. What is the maximum number of photos per inspection (storage/bandwidth considerations)?
+4. Should the PWA support Web Push notifications (requires VAPID key configuration)?
+5. How does offline sync handle photo uploads -- queue all or sync progressively?
+6. Should the PWA support multiple simultaneous offline inspections?
diff --git a/openspec/changes/mobiel-inspectie/tasks.md b/openspec/changes/archive/2026-05-11-mobiel-inspectie/tasks.md
similarity index 100%
rename from openspec/changes/mobiel-inspectie/tasks.md
rename to openspec/changes/archive/2026-05-11-mobiel-inspectie/tasks.md
diff --git a/openspec/changes/multi-tenant-saas/.openspec.yaml b/openspec/changes/archive/2026-05-11-multi-tenant-saas/.openspec.yaml
similarity index 100%
rename from openspec/changes/multi-tenant-saas/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-multi-tenant-saas/.openspec.yaml
diff --git a/openspec/changes/multi-tenant-saas/builds/build.json b/openspec/changes/archive/2026-05-11-multi-tenant-saas/builds/build.json
similarity index 100%
rename from openspec/changes/multi-tenant-saas/builds/build.json
rename to openspec/changes/archive/2026-05-11-multi-tenant-saas/builds/build.json
diff --git a/openspec/changes/multi-tenant-saas/design.md b/openspec/changes/archive/2026-05-11-multi-tenant-saas/design.md
similarity index 100%
rename from openspec/changes/multi-tenant-saas/design.md
rename to openspec/changes/archive/2026-05-11-multi-tenant-saas/design.md
diff --git a/openspec/changes/multi-tenant-saas/proposal.md b/openspec/changes/archive/2026-05-11-multi-tenant-saas/proposal.md
similarity index 100%
rename from openspec/changes/multi-tenant-saas/proposal.md
rename to openspec/changes/archive/2026-05-11-multi-tenant-saas/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-multi-tenant-saas/specs/multi-tenant-saas/spec.md b/openspec/changes/archive/2026-05-11-multi-tenant-saas/specs/multi-tenant-saas/spec.md
new file mode 100644
index 00000000..437f157f
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-multi-tenant-saas/specs/multi-tenant-saas/spec.md
@@ -0,0 +1,300 @@
+---
+status: implemented
+---
+# multi-tenant-saas Specification
+
+## Purpose
+Enable logical data isolation for multiple municipalities on a single Procest/Nextcloud deployment. Each tenant has its own users, cases, configuration, and branding while sharing the platform infrastructure. Cross-tenant access is restricted to platform administrators.
+
+## Context
+The SaaS delivery model (shared platform) requires serving multiple municipalities from a single deployment. This reduces operational overhead and enables shared updates. Nextcloud's native architecture is single-instance, but OpenRegister's register model provides a natural isolation boundary: each municipality gets its own register(s), and RBAC enforces access control. The `SettingsService` currently manages a single `procest` register with 26 schemas (case, task, status, role, result, decision, caseType, etc.) -- multi-tenancy requires replicating this register structure per tenant.
+
+## ADDED Requirements
+### Requirement: Tenant data isolation via OpenRegister registers
+The system MUST ensure complete logical data isolation between tenants by assigning each tenant a dedicated OpenRegister register with its own schema set.
+
+#### Scenario: Tenant-scoped queries return only tenant data
+- **GIVEN** municipality A and municipality B each have their own OpenRegister register
+- **WHEN** a case worker from municipality A queries cases via the Procest API
+- **THEN** only cases stored in municipality A's register MUST be returned
+- **AND** no data from municipality B MUST be visible in any API response or UI view
+- **AND** the register ID used for queries MUST be resolved from the user's tenant context
+
+#### Scenario: Tenant-scoped object creation stamps register automatically
+- **GIVEN** a case worker from municipality A creating a new case
+- **WHEN** the case is saved via OpenRegister's ObjectService
+- **THEN** the case MUST be stored in municipality A's register
+- **AND** the register ID MUST be resolved automatically from the user's tenant membership
+- **AND** the case worker MUST NOT be able to select or override the target register
+
+#### Scenario: Cross-tenant access returns 404 to prevent information leakage
+- **GIVEN** a case worker from municipality A who knows a case UUID from municipality B
+- **WHEN** they attempt to access that case via direct API call (e.g., `GET /api/objects/{register}/{schema}/{uuid}`)
+- **THEN** the system MUST return HTTP 404 (not 403, to prevent confirming the object exists)
+- **AND** the access attempt MUST be logged in the security audit trail
+
+#### Scenario: ZGW API endpoints enforce tenant scoping
+- **GIVEN** the ZrcController, ZtcController, BrcController, and DrcController serve ZGW-compatible APIs
+- **WHEN** an external system authenticates and queries cases via ZGW endpoints
+- **THEN** the ZgwService MUST resolve the tenant's register and schema IDs from the authenticated context
+- **AND** cross-tenant data MUST never be returned even if the external system provides valid object references
+
+#### Scenario: Database-level query isolation
+- **GIVEN** OpenRegister stores all tenants' objects in the same PostgreSQL database
+- **WHEN** any query is executed against the objects table
+- **THEN** the query MUST include a register ID filter as a mandatory WHERE clause
+- **AND** no query path (including search, listing, and aggregation) MUST bypass the register filter
+
+### Requirement: Tenant identity resolution via Nextcloud groups
+The system MUST determine a user's tenant based on Nextcloud group membership, using a configurable group naming convention.
+
+#### Scenario: User belongs to exactly one tenant group
+- **GIVEN** user "j.jansen" is a member of Nextcloud group `tenant_gemeente_utrecht`
+- **AND** the tenant group prefix is configured as `tenant_`
+- **WHEN** "j.jansen" accesses Procest
+- **THEN** the system MUST resolve their tenant as "gemeente_utrecht"
+- **AND** load the corresponding register ID from the tenant configuration
+
+#### Scenario: User belongs to no tenant group
+- **GIVEN** user "admin" has no group matching the `tenant_` prefix
+- **WHEN** "admin" accesses Procest
+- **THEN** the system MUST deny access to case management features
+- **AND** display a message: "U bent niet gekoppeld aan een organisatie. Neem contact op met uw beheerder."
+
+#### Scenario: User belongs to multiple tenant groups (platform admin)
+- **GIVEN** user "p.admin" is a member of groups `tenant_gemeente_utrecht` and `tenant_gemeente_amsterdam` and `platform_admin`
+- **WHEN** "p.admin" accesses Procest
+- **THEN** the system MUST present a tenant selector in the navigation header
+- **AND** all actions MUST operate within the selected tenant's context
+- **AND** tenant switches MUST be logged in the audit trail
+
+### Requirement: Tenant-independent configuration per zaaktype
+Each tenant MUST have independent configuration for zaaktypes, status types, result types, role types, and decision types.
+
+#### Scenario: Tenant-specific zaaktype definitions
+- **GIVEN** municipality A has zaaktype "Evenementenvergunning" with 5 status types and 3-week processing deadline
+- **AND** municipality B has zaaktype "Evenementenvergunning" with 3 status types and 6-week processing deadline
+- **WHEN** each municipality's case workers view available zaaktypes
+- **THEN** each MUST see only their own municipality's configuration
+- **AND** the configurations MUST be stored as separate caseType objects in each tenant's register
+
+#### Scenario: Tenant configuration does not leak between tenants
+- **GIVEN** municipality A creates a new status type "Wacht op extern advies"
+- **WHEN** municipality B's admin views their status types
+- **THEN** municipality B MUST NOT see municipality A's new status type
+- **AND** the settings API (`/apps/procest/api/settings`) MUST return tenant-scoped schema IDs
+
+#### Scenario: Tenant admin manages configuration independently
+- **GIVEN** a tenant admin for municipality A accesses Settings > Case Types
+- **WHEN** they modify the "Omgevingsvergunning" zaaktype
+- **THEN** only municipality A's configuration MUST be affected
+- **AND** the change MUST be logged with the tenant admin's identity and tenant context
+
+### Requirement: Per-tenant branding via NL Design System tokens
+Each tenant MUST be able to apply its own visual identity using NL Design System design tokens.
+
+#### Scenario: Tenant-specific logo and color scheme
+- **GIVEN** municipality A has logo "gemeente-a.svg" and primary color `#003366`
+- **AND** municipality B has logo "gemeente-b.svg" and primary color `#009933`
+- **WHEN** each municipality's users access Procest
+- **THEN** the UI MUST display the correct logo and color scheme per tenant
+- **AND** NL Design System tokens MUST be loaded per tenant from the tenant's configuration
+
+#### Scenario: Tenant branding applies to public-facing pages
+- **GIVEN** a citizen accesses a shared case link for a municipality A case
+- **WHEN** the public case view loads
+- **THEN** the branding MUST reflect municipality A's design tokens
+- **AND** the branding MUST NOT show Procest or platform branding unless configured
+
+#### Scenario: Tenant without custom branding uses defaults
+- **GIVEN** a new tenant has not configured custom branding
+- **WHEN** users access Procest
+- **THEN** the default NL Design System theme (Rijksoverheid tokens) MUST be applied
+- **AND** a warning MUST appear in the tenant admin panel: "Huisstijl niet geconfigureerd"
+
+### Requirement: Tenant user management scoped to organization
+Users MUST be scoped to their tenant with appropriate access controls; tenant admins manage only their own users.
+
+#### Scenario: Tenant admin sees only their own users
+- **GIVEN** a tenant admin for municipality A
+- **WHEN** they access user management in Procest settings
+- **THEN** they MUST only see users who are members of the `tenant_gemeente_a` Nextcloud group
+- **AND** they MUST be able to assign roles (behandelaar, coordinator, admin) within the tenant
+
+#### Scenario: Platform admin cross-tenant access with audit trail
+- **GIVEN** a platform administrator with the `platform_admin` group
+- **WHEN** they access the admin panel
+- **THEN** they MUST see a tenant overview with user counts, case counts, and storage usage per tenant
+- **AND** they MUST be able to switch between tenants via a tenant selector
+- **AND** all cross-tenant actions MUST be logged with the platform admin's identity and the target tenant
+
+#### Scenario: User deactivation scopes to tenant only
+- **GIVEN** tenant admin deactivates user "m.bakker" in municipality A
+- **WHEN** "m.bakker" is also a member of another tenant group (unusual but possible)
+- **THEN** only their membership in municipality A's tenant group MUST be affected
+- **AND** their access to other tenants MUST remain unchanged
+
+### Requirement: Automated tenant provisioning
+The platform MUST support creating and configuring new tenants through an API and admin interface.
+
+#### Scenario: Provision new tenant with default configuration
+- **GIVEN** a platform administrator
+- **WHEN** they create a new tenant with name "Gemeente Eindhoven", OIN "00000001002306608000", and slug "gemeente-eindhoven"
+- **THEN** the system MUST create a Nextcloud group `tenant_gemeente-eindhoven`
+- **AND** create a dedicated OpenRegister register with all 26 Procest schemas (mirroring `procest_register.json`)
+- **AND** store the register ID and schema IDs in a tenant configuration record
+- **AND** create a tenant admin user account assigned to the new group
+- **AND** the provisioning MUST complete within 30 seconds
+
+#### Scenario: Tenant provisioning via API
+- **GIVEN** the provisioning API endpoint `POST /api/admin/tenants`
+- **WHEN** called with `{"name": "Gemeente Eindhoven", "oin": "00000001002306608000", "slug": "gemeente-eindhoven", "adminEmail": "admin@eindhoven.nl"}`
+- **THEN** the system MUST provision the tenant as described above
+- **AND** return the tenant configuration including register ID and admin credentials
+- **AND** send a welcome email to the admin email address
+
+#### Scenario: Tenant provisioning is idempotent
+- **GIVEN** tenant "gemeente-eindhoven" already exists
+- **WHEN** the provisioning API is called again with the same slug
+- **THEN** the system MUST return HTTP 409 Conflict
+- **AND** the existing tenant MUST NOT be modified
+
+### Requirement: Tenant resource limits and usage monitoring
+The platform MUST enforce configurable resource limits per tenant and provide usage dashboards.
+
+#### Scenario: User limit enforcement
+- **GIVEN** a tenant configuration with max users set to 50
+- **AND** the tenant currently has 50 active users
+- **WHEN** the tenant admin attempts to add a 51st user
+- **THEN** the system MUST reject the addition with message "Gebruikerslimiet bereikt (50/50)"
+- **AND** the platform admin MUST be notified
+
+#### Scenario: Storage quota enforcement
+- **GIVEN** a tenant configuration with max storage set to 10 GB
+- **AND** current usage is 9.8 GB
+- **WHEN** a case worker uploads a 300 MB document
+- **THEN** the system MUST reject the upload with message "Opslaglimiet bijna bereikt"
+- **AND** the Nextcloud quota system MUST enforce the limit at the group level
+
+#### Scenario: Usage dashboard shows current vs. limits
+- **GIVEN** a tenant admin views their settings dashboard
+- **THEN** the dashboard MUST show: active users (42/50), storage used (7.2 GB / 10 GB), total cases (1,247), total documents (3,891)
+- **AND** items approaching 80% of limit MUST be highlighted in amber
+- **AND** items exceeding 90% of limit MUST be highlighted in red
+
+### Requirement: Shared resources and template library
+Certain resources MUST be shareable across tenants for efficiency while maintaining tenant isolation on copies.
+
+#### Scenario: Platform-level zaaktype template activation
+- **GIVEN** a platform-level zaaktype template "WOO verzoek" maintained by the platform admin
+- **WHEN** a tenant admin activates the template for their tenant
+- **THEN** the template MUST be deep-copied into the tenant's register as a new caseType object
+- **AND** all associated status types, result types, and role types MUST also be copied
+- **AND** modifications to the tenant's copy MUST NOT affect other tenants or the source template
+
+#### Scenario: Template versioning and updates
+- **GIVEN** a platform template "WOO verzoek" is updated from v1.2 to v1.3
+- **AND** tenants A and B both have local copies based on v1.2
+- **WHEN** the platform admin publishes the update
+- **THEN** tenant admins MUST see a notification: "Template 'WOO verzoek' heeft een update (v1.3)"
+- **AND** they MUST be able to review changes and choose to apply or skip the update
+- **AND** applying the update MUST NOT overwrite tenant-specific customizations without confirmation
+
+#### Scenario: Shared reference data remains read-only
+- **GIVEN** platform-level reference data (e.g., BAG address lookup, BRP integration endpoints)
+- **WHEN** a tenant admin views the reference data
+- **THEN** it MUST be read-only at the tenant level
+- **AND** changes MUST only be possible by the platform admin
+
+### Requirement: Tenant deactivation and data retention
+The platform MUST support deactivating tenants while preserving data according to retention policies.
+
+#### Scenario: Deactivate tenant
+- **GIVEN** a platform administrator deactivates tenant "gemeente-eindhoven"
+- **WHEN** the deactivation is processed
+- **THEN** all users in the tenant's Nextcloud group MUST be blocked from accessing Procest
+- **AND** the tenant's data MUST remain in OpenRegister (not deleted) for the configured retention period
+- **AND** the tenant MUST NOT appear in active tenant listings but MUST appear in the archive
+
+#### Scenario: Reactivate tenant within retention period
+- **GIVEN** tenant "gemeente-eindhoven" was deactivated 30 days ago
+- **AND** the retention period is 365 days
+- **WHEN** the platform admin reactivates the tenant
+- **THEN** all data MUST be restored to active state
+- **AND** users MUST regain access with their previous roles
+
+#### Scenario: Data purge after retention period
+- **GIVEN** tenant "gemeente-eindhoven" was deactivated 366 days ago
+- **AND** the retention period is 365 days
+- **WHEN** the scheduled purge job runs
+- **THEN** the platform admin MUST receive a confirmation prompt before purging
+- **AND** upon confirmation, all tenant data MUST be permanently deleted from OpenRegister
+- **AND** a purge certificate MUST be generated for compliance records
+
+### Requirement: Cross-tenant reporting for platform administrators
+Platform administrators MUST be able to generate aggregated reports across all tenants without accessing individual case data.
+
+#### Scenario: Platform-wide KPI dashboard
+- **GIVEN** the platform serves 12 municipalities
+- **WHEN** the platform admin views the platform dashboard
+- **THEN** aggregated metrics MUST be shown: total active cases per tenant, average processing time per tenant, SLA compliance percentage per tenant
+- **AND** no individual case details MUST be visible
+
+#### Scenario: Tenant comparison report
+- **GIVEN** the platform admin requests a comparison report
+- **WHEN** the report is generated
+- **THEN** it MUST show per-tenant: case volume, average resolution time, overdue percentage, user activity
+- **AND** the report MUST be exportable as CSV and PDF
+
+#### Scenario: Anomaly detection alerts
+- **GIVEN** tenant "gemeente-utrecht" normally processes 200 cases/month
+- **AND** the current month shows 50 cases (75% drop)
+- **WHEN** the daily anomaly check runs
+- **THEN** the platform admin MUST receive an alert highlighting the unusual pattern
+
+## Non-Requirements
+- This spec does NOT cover database-per-tenant isolation (PostgreSQL schemas or separate databases)
+- This spec does NOT cover multi-region deployment or data residency requirements
+- This spec does NOT cover billing or subscription management
+- This spec does NOT cover SSO federation between tenant identity providers
+
+## Dependencies
+- OpenRegister registers as tenant isolation boundary
+- OpenRegister RBAC for access control enforcement
+- NL Design System tokens for per-tenant branding
+- Nextcloud group-based user management (`OCP\IGroupManager`)
+- Nextcloud quota system for storage limits
+- SettingsService for per-tenant register/schema ID resolution
+- ConfigurationService for automated register provisioning
+
+---
+
+### Current Implementation Status
+
+**Not yet implemented.** Multi-tenancy is not currently built into Procest. The following foundational elements exist that could support future multi-tenant work:
+
+- The app uses a single `procest` register in OpenRegister (defined in `lib/Settings/procest_register.json`). Tenant isolation would require creating per-tenant registers using `ConfigurationService::importFromApp()` with tenant-specific parameters.
+- The `InitializeSettings` repair step (`lib/Repair/InitializeSettings.php`) creates the register via `SettingsService.loadConfiguration()` but does not support tenant-scoped register creation.
+- The `SettingsService` stores register and schema IDs as global app config keys (e.g., `register`, `case_schema`) via `IAppConfig`. Multi-tenancy requires per-tenant config storage (e.g., keyed by tenant slug).
+- The frontend object store (`src/store/modules/object.js`) uses `createObjectStore('object')` from `@conduction/nextcloud-vue` which queries a single register/schema pair. No tenant switching logic exists.
+- The settings store (`src/store/modules/settings.js`) fetches from `/apps/procest/api/settings` -- a single global config, not per-tenant.
+- No tenant provisioning UI or API exists.
+- NL Design System theming is supported at the app level via the `nldesign` submodule, but not per-tenant.
+- Nextcloud groups exist but are not used for tenant scoping in Procest.
+
+**Partial foundations:**
+- OpenRegister's register model inherently supports data isolation (one register per tenant is the natural boundary).
+- ZGW API controllers (`lib/Controller/ZrcController.php`, `ZtcController.php`, etc.) use `ZgwService` which reads register/schema IDs from settings -- these could be made tenant-aware by resolving settings per-tenant.
+
+### Standards & References
+
+- **ZGW APIs** (VNG Realisatie): Multi-tenant ZGW deployments are common in Dutch government; the Catalogi API supports multiple catalogues per instance.
+- **Common Ground**: The information layer principle supports data isolation via separate registers per organization.
+- **ISO 27001**: Data isolation requirements for SaaS platforms handling government data.
+- **BIO (Baseline Informatiebeveiliging Overheid)**: Dutch government security baseline requiring logical data separation between organizations.
+- **AVG/GDPR**: Data processing agreements require clear tenant boundaries for personal data.
+- **NL Design System**: Per-organization theming tokens are a standard pattern in Dutch government web applications.
+- **Nextcloud multi-tenant patterns**: Nextcloud does not natively support multi-tenancy; this is typically achieved via app-level isolation using groups.
+- **CMMN 1.1**: No direct multi-tenancy concept, but case plans are scoped to organizational context.
+- **Dimpact ZAC**: Uses separate Open Zaak instances per municipality -- a database-per-tenant approach. Procest's register-per-tenant is a lighter-weight alternative.
+- **Valtimo/Ritense**: Uses Spring Security multi-tenancy with separate database schemas -- similar logical isolation to OpenRegister's register model.
diff --git a/openspec/changes/multi-tenant-saas/tasks.md b/openspec/changes/archive/2026-05-11-multi-tenant-saas/tasks.md
similarity index 100%
rename from openspec/changes/multi-tenant-saas/tasks.md
rename to openspec/changes/archive/2026-05-11-multi-tenant-saas/tasks.md
diff --git a/openspec/changes/my-work/builds/build.json b/openspec/changes/archive/2026-05-11-my-work/builds/build.json
similarity index 100%
rename from openspec/changes/my-work/builds/build.json
rename to openspec/changes/archive/2026-05-11-my-work/builds/build.json
diff --git a/openspec/changes/my-work/delta-spec.md b/openspec/changes/archive/2026-05-11-my-work/delta-spec.md
similarity index 100%
rename from openspec/changes/my-work/delta-spec.md
rename to openspec/changes/archive/2026-05-11-my-work/delta-spec.md
diff --git a/openspec/changes/my-work/design.md b/openspec/changes/archive/2026-05-11-my-work/design.md
similarity index 100%
rename from openspec/changes/my-work/design.md
rename to openspec/changes/archive/2026-05-11-my-work/design.md
diff --git a/openspec/changes/my-work/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-my-work/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/my-work/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-my-work/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/my-work/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-my-work/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/my-work/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-my-work/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/my-work/proposal.md b/openspec/changes/archive/2026-05-11-my-work/proposal.md
similarity index 100%
rename from openspec/changes/my-work/proposal.md
rename to openspec/changes/archive/2026-05-11-my-work/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-my-work/specs/my-work/spec.md b/openspec/changes/archive/2026-05-11-my-work/specs/my-work/spec.md
new file mode 100644
index 00000000..62673efd
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-my-work/specs/my-work/spec.md
@@ -0,0 +1,118 @@
+---
+status: implemented
+---
+# my-work Specification
+
+## Purpose
+Document the polish layer applied to the existing "Mijn werk" view in Procest: case-type name display on case items, ARIA labels and keyboard navigation for WCAG AA conformance, and a responsive layout for narrow viewports. The personal workload aggregation and filtering surface itself was shipped earlier and is captured in the canonical `my-work` capability spec; this change formalizes the non-functional requirements applied on top.
+
+## Context
+`MyWork.vue` renders a per-user list of cases and tasks grouped by urgency (overdue, today, this week, later). Items previously showed only entity type and title, with limited keyboard support. This change resolves case type names at mount, attaches ARIA semantics to tabs and items, exposes keyboard activation, and stacks rows on narrow viewports.
+
+## ADDED Requirements
+### Requirement: REQ-MYWORK-CT-01 — Case Type Name on Case Items
+Case items in the "Mijn werk" view MUST display the case type name as a subtitle.
+
+#### Scenario: MYWORK-CT-01-1: Case type resolved from cached collection
+- **GIVEN** a user opens "Mijn werk"
+- **WHEN** the view mounts
+- **THEN** `objectStore.fetchCollection('caseType')` MUST be invoked once to build a lookup map from case type id to title
+- **AND** each case item MUST display the resolved case type title (e.g., "Omgevingsvergunning") below its title
+
+#### Scenario: MYWORK-CT-01-2: Unknown or missing case type
+- **GIVEN** a case item references a case type id not present in the lookup
+- **THEN** the subtitle MUST render an empty placeholder (no broken label, no console error)
+
+#### Scenario: MYWORK-CT-01-3: Task items unaffected
+- **GIVEN** a mix of case and task items
+- **THEN** only case items MUST show the case-type subtitle
+- **AND** task items MUST continue to show their existing subtitle (e.g., linked case reference)
+
+### Requirement: REQ-MYWORK-A11Y-01 — ARIA Semantics
+The "Mijn werk" view MUST expose ARIA semantics that allow screen readers to announce structure and state.
+
+#### Scenario: MYWORK-A11Y-01-1: Filter tabs use tab pattern
+- **GIVEN** the filter bar at the top of the view
+- **THEN** the container MUST have `role="tablist"`
+- **AND** each filter button MUST have `role="tab"` with `aria-selected` reflecting the active state
+
+#### Scenario: MYWORK-A11Y-01-2: Section headers expose heading role
+- **GIVEN** grouped sections "Overdue", "Today", "This week", "Later"
+- **THEN** each section header MUST have `role="heading"` with an appropriate `aria-level`
+
+#### Scenario: MYWORK-A11Y-01-3: Item aria-label
+- **GIVEN** a case item with title "Bouwvergunning Kerkstraat" that is overdue
+- **THEN** the item MUST have an `aria-label` announcing entity type, title, and urgency status (e.g., "Zaak: Bouwvergunning Kerkstraat, te laat")
+
+#### Scenario: MYWORK-A11Y-01-4: Live region for overdue
+- **GIVEN** urgency text that changes (e.g., when the day rolls over)
+- **THEN** the overdue indicator MUST be in an `aria-live="polite"` region so updates are announced without stealing focus
+
+### Requirement: REQ-MYWORK-KB-01 — Keyboard Navigation
+The "Mijn werk" view MUST be fully operable from the keyboard.
+
+#### Scenario: MYWORK-KB-01-1: Tab to items
+- **GIVEN** the view is rendered
+- **THEN** every item row MUST have `tabindex="0"`
+- **AND** pressing Tab MUST move focus through items in their visual order
+
+#### Scenario: MYWORK-KB-01-2: Activate item with Enter/Space
+- **GIVEN** an item row has keyboard focus
+- **WHEN** the user presses Enter or Space
+- **THEN** the same navigation MUST occur as a mouse click on the item
+
+#### Scenario: MYWORK-KB-01-3: Activate filter tab with Enter/Space
+- **GIVEN** a filter tab has keyboard focus
+- **WHEN** the user presses Enter or Space
+- **THEN** the corresponding filter MUST become active and `aria-selected` MUST update
+
+### Requirement: REQ-MYWORK-RWD-01 — Responsive Layout
+The "Mijn werk" view MUST remain readable on narrow viewports.
+
+#### Scenario: MYWORK-RWD-01-1: Stack rows at 768px or below
+- **GIVEN** a viewport of 768 CSS pixels or narrower
+- **THEN** item rows MUST stack their content vertically (title on top, priority and deadline below)
+- **AND** padding MUST reduce to remain comfortable on mobile
+
+#### Scenario: MYWORK-RWD-01-2: Filter tabs remain operable
+- **GIVEN** the mobile layout is active
+- **THEN** filter tabs MUST remain visible and tappable
+- **AND** each tap target MUST be at least 44x44 CSS pixels
+
+### Requirement: REQ-MYWORK-FOCUS-01 — Focus Management
+The "Mijn werk" view MUST manage focus predictably when items, filters, or sections change.
+
+#### Scenario: MYWORK-FOCUS-01-1: Focus preserved across filter change
+- **GIVEN** a filter tab has keyboard focus
+- **WHEN** the user activates a different filter
+- **THEN** focus MUST remain on the newly active filter tab
+- **AND** the focus ring MUST be visible
+
+#### Scenario: MYWORK-FOCUS-01-2: Focus visible on item
+- **GIVEN** an item row receives keyboard focus
+- **THEN** the row MUST render a clearly visible focus indicator that meets WCAG AA contrast requirements
+- **AND** the indicator MUST NOT rely on colour alone
+
+#### Scenario: MYWORK-FOCUS-01-3: Skip duplicate focusables
+- **GIVEN** an item that contains nested interactive controls (e.g., a quick-action button)
+- **THEN** the outer row MUST be focusable as a single stop
+- **AND** tabbing into the row MUST NOT trap the user in repeated nested stops
+
+### Requirement: REQ-MYWORK-INT-01 — Backwards-Compatible Integration
+The polish layer MUST NOT regress existing "Mijn werk" behaviour.
+
+#### Scenario: MYWORK-INT-01-1: Existing sections preserved
+- **GIVEN** the existing grouped sections, filters, and counts
+- **WHEN** this change is applied
+- **THEN** those affordances MUST continue to work unchanged
+- **AND** no new dependencies on additional stores or APIs MUST be introduced beyond the existing object store
+
+#### Scenario: MYWORK-INT-01-2: Single fetch on mount
+- **GIVEN** the case-type lookup is built on mount
+- **THEN** the fetch MUST occur at most once per view mount
+- **AND** subsequent renders MUST reuse the cached lookup
+
+## Dependencies
+- `src/views/MyWork.vue`
+- Shared object store (`createObjectStore('caseType')`)
+- Existing canonical `my-work` capability spec (`openspec/specs/my-work/spec.md`) for the underlying workload, filter, sort, and grouping behaviour
diff --git a/openspec/changes/my-work/tasks.md b/openspec/changes/archive/2026-05-11-my-work/tasks.md
similarity index 100%
rename from openspec/changes/my-work/tasks.md
rename to openspec/changes/archive/2026-05-11-my-work/tasks.md
diff --git a/openspec/changes/openregister-integration/builds/build.json b/openspec/changes/archive/2026-05-11-openregister-integration/builds/build.json
similarity index 100%
rename from openspec/changes/openregister-integration/builds/build.json
rename to openspec/changes/archive/2026-05-11-openregister-integration/builds/build.json
diff --git a/openspec/changes/openregister-integration/design.md b/openspec/changes/archive/2026-05-11-openregister-integration/design.md
similarity index 100%
rename from openspec/changes/openregister-integration/design.md
rename to openspec/changes/archive/2026-05-11-openregister-integration/design.md
diff --git a/openspec/changes/openregister-integration/proposal.md b/openspec/changes/archive/2026-05-11-openregister-integration/proposal.md
similarity index 100%
rename from openspec/changes/openregister-integration/proposal.md
rename to openspec/changes/archive/2026-05-11-openregister-integration/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-openregister-integration/specs/openregister-integration/spec.md b/openspec/changes/archive/2026-05-11-openregister-integration/specs/openregister-integration/spec.md
new file mode 100644
index 00000000..23b83b79
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-openregister-integration/specs/openregister-integration/spec.md
@@ -0,0 +1,30 @@
+# Delta: openregister-integration
+
+## ADDED Requirements
+### Requirement: Schema Registration — Enhanced
+The system SHALL satisfy the behaviour described as "Schema Registration — Enhanced".
+
+- Refactored store.js from 13 individual conditional registrations to data-driven pattern
+- Now registers all 27 schemas defined in the spec (configuration + instance + ZGW support)
+- Added: statusRecord, catalogus, zaaktypeInformatieobjecttype, caseProperty, caseDocument,
+ caseObject, customerContact, decisionDocument, dispatch, document, documentLink, usageRights,
+ kanaal, abonnement
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: Frontend Availability Check — New
+The system SHALL satisfy the behaviour described as "Frontend Availability Check — New".
+
+- Created `src/utils/openregisterCheck.js` with `checkOpenRegisterStatus()` and `getStatusMessage()`
+- Checks both OpenRegister app availability and Procest register configuration
+- Returns localized status messages for admin guidance
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
diff --git a/openspec/changes/openregister-integration/tasks.md b/openspec/changes/archive/2026-05-11-openregister-integration/tasks.md
similarity index 100%
rename from openspec/changes/openregister-integration/tasks.md
rename to openspec/changes/archive/2026-05-11-openregister-integration/tasks.md
diff --git a/openspec/changes/parafeerroute-engine/.openspec.yaml b/openspec/changes/archive/2026-05-11-parafeerroute-engine/.openspec.yaml
similarity index 100%
rename from openspec/changes/parafeerroute-engine/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/.openspec.yaml
diff --git a/openspec/changes/parafeerroute-engine/builds/build.json b/openspec/changes/archive/2026-05-11-parafeerroute-engine/builds/build.json
similarity index 100%
rename from openspec/changes/parafeerroute-engine/builds/build.json
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/builds/build.json
diff --git a/openspec/changes/parafeerroute-engine/context-brief.md b/openspec/changes/archive/2026-05-11-parafeerroute-engine/context-brief.md
similarity index 100%
rename from openspec/changes/parafeerroute-engine/context-brief.md
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/context-brief.md
diff --git a/openspec/changes/parafeerroute-engine/design.md b/openspec/changes/archive/2026-05-11-parafeerroute-engine/design.md
similarity index 100%
rename from openspec/changes/parafeerroute-engine/design.md
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/design.md
diff --git a/openspec/changes/parafeerroute-engine/hydra.json b/openspec/changes/archive/2026-05-11-parafeerroute-engine/hydra.json
similarity index 100%
rename from openspec/changes/parafeerroute-engine/hydra.json
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/hydra.json
diff --git a/openspec/changes/parafeerroute-engine/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/parafeerroute-engine/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/parafeerroute-engine/pipeline-logs/build.2.jsonl.gz b/openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.2.jsonl.gz
similarity index 100%
rename from openspec/changes/parafeerroute-engine/pipeline-logs/build.2.jsonl.gz
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.2.jsonl.gz
diff --git a/openspec/changes/parafeerroute-engine/pipeline-logs/build.3.jsonl.gz b/openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.3.jsonl.gz
similarity index 100%
rename from openspec/changes/parafeerroute-engine/pipeline-logs/build.3.jsonl.gz
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.3.jsonl.gz
diff --git a/openspec/changes/parafeerroute-engine/pipeline-logs/build.4.jsonl.gz b/openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.4.jsonl.gz
similarity index 100%
rename from openspec/changes/parafeerroute-engine/pipeline-logs/build.4.jsonl.gz
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.4.jsonl.gz
diff --git a/openspec/changes/parafeerroute-engine/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/parafeerroute-engine/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/parafeerroute-engine/proposal.md b/openspec/changes/archive/2026-05-11-parafeerroute-engine/proposal.md
similarity index 100%
rename from openspec/changes/parafeerroute-engine/proposal.md
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/proposal.md
diff --git a/openspec/changes/parafeerroute-engine/specs/parafeerroute-engine/spec.md b/openspec/changes/archive/2026-05-11-parafeerroute-engine/specs/parafeerroute-engine/spec.md
similarity index 92%
rename from openspec/changes/parafeerroute-engine/specs/parafeerroute-engine/spec.md
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/specs/parafeerroute-engine/spec.md
index 2bb93f97..ed420b84 100644
--- a/openspec/changes/parafeerroute-engine/specs/parafeerroute-engine/spec.md
+++ b/openspec/changes/archive/2026-05-11-parafeerroute-engine/specs/parafeerroute-engine/spec.md
@@ -11,17 +11,16 @@ Implement the parafeerroute configuration and execution engine within Procest. T
Dutch municipal B&W decision-making relies on structured approval chains (parafeerroutes) where proposals pass sequentially through adviseurs, parafeerders, and accorderende partijen before reaching the college. The `bw-parafering` spec established the data model (`voorstel`, `parafeerroute`, `parafeeractie` in ADR-000). This change implements the engine: schema registration, sequential execution, admin configuration UI, and runtime override capabilities. The `routeSnapshot` pattern ensures that edits to a parafeerroute template do not affect voorstellen already in flight.
-## Requirements
-
+## ADDED Requirements
---
-### REQ-PRE-001: Parafeerroute Schema Registration
+### Requirement: REQ-PRE-001 — Parafeerroute Schema Registration
The system SHALL register a `parafeerroute` schema in the Procest OpenRegister configuration with properties: `name` (string, required), `caseType` (reference), `voorstelType` (enum: `dt_advies`, `collegeadvies`, `raadsvoorstel`), `steps` (array of `parafeerstap` objects, required), `isDefault` (boolean), `description` (string). Each `parafeerstap` SHALL have: `order` (integer), `type` (enum: `advies`, `parafering`, `accordering`), `actor` (string — user UID or group/role name), `actorType` (enum: `user`, `group`, `role`), `mandatory` (boolean).
**Feature tier**: V1
-#### REQ-PRE-001-001: Schema available after app install
+#### Scenario: Schema available after app install
- **GIVEN** the Procest app is installed or updated
- **WHEN** the app repair step runs
@@ -31,13 +30,13 @@ The system SHALL register a `parafeerroute` schema in the Procest OpenRegister c
---
-### REQ-PRE-002: Sequential Step Routing
+### Requirement: REQ-PRE-002 — Sequential Step Routing
The system SHALL execute parafeerroute steps in sequential order. Each step SHALL complete before the next step is activated. The `routeSnapshot` SHALL be captured on the voorstel at submission time so that subsequent edits to the parafeerroute template do not affect in-flight voorstellen.
**Feature tier**: V1
-#### REQ-PRE-002-001: Sequential step execution
+#### Scenario: Sequential step execution
- **GIVEN** a voorstel is submitted for parafering with a linked parafeerroute containing 5 steps
- **WHEN** the steller submits the voorstel (status transitions to `in_parafering`)
@@ -46,7 +45,7 @@ The system SHALL execute parafeerroute steps in sequential order. Each step SHAL
- **AND** the system SHALL activate step 1: create a Nextcloud task for the step 1 actor linked to the case, and send a notification: "Voorstel '[onderwerp]' wacht op uw [type] (stap 1 van 5)"
- **AND** step 2 SHALL NOT be activated until step 1 is completed
-#### REQ-PRE-002-002: Step completion advances to next
+#### Scenario: Step completion advances to next
- **GIVEN** the actor at step 3 completes their action (paraferen or adviseren) on a 5-step voorstel
- **WHEN** the `parafeeractie` is recorded with `action` = `parafered` or `advised`
@@ -55,7 +54,7 @@ The system SHALL execute parafeerroute steps in sequential order. Each step SHAL
- **AND** a task SHALL be created for the step 4 actor
- **AND** `voorstel.updatedAt` SHALL be refreshed
-#### REQ-PRE-002-003: Final step completion marks voorstel geaccordeerd
+#### Scenario: Final step completion marks voorstel geaccordeerd
- **GIVEN** a voorstel at its final step (e.g. step 5 of 5) of type `accordering`
- **WHEN** the accordering actor records their action
@@ -65,13 +64,13 @@ The system SHALL execute parafeerroute steps in sequential order. Each step SHAL
---
-### REQ-PRE-003: Admin Parafeerroute Configuration
+### Requirement: REQ-PRE-003 — Admin Parafeerroute Configuration
The system SHALL provide an admin UI for creating and managing parafeerroutes. Routes SHALL be linkable to case types and voorstel types. Each caseType + voorstelType combination SHALL support at most one default route.
**Feature tier**: V1
-#### REQ-PRE-003-001: Create new parafeerroute
+#### Scenario: Create new parafeerroute
- **GIVEN** the beheerder navigates to admin settings and opens the "Parafeerroutes" tab
- **WHEN** the beheerder clicks "Nieuwe route", fills in: name, voorstelType, optional caseType, optional isDefault flag, optional description, and adds steps via the step editor
@@ -81,14 +80,14 @@ The system SHALL provide an admin UI for creating and managing parafeerroutes. R
- **AND** the route SHALL appear in the list under the "Parafeerroutes" tab
- **AND** if `isDefault` is true and another route with the same caseType + voorstelType is already the default, the previous default's `isDefault` SHALL be set to false
-#### REQ-PRE-003-002: Link route to case type and voorstel type
+#### Scenario: Link route to case type and voorstel type
- **GIVEN** the beheerder has created a parafeerroute with `voorstelType` = `collegeadvies` and `caseType` linked to "Omgevingsvergunning", with `isDefault` = true
- **WHEN** a steller creates a voorstel of type `collegeadvies` on a case of that case type
- **THEN** the linked route SHALL be pre-loaded as the default parafeerroute on the voorstel
- **AND** the steller SHALL be able to change the route before submission
-#### REQ-PRE-003-003: Edit existing parafeerroute
+#### Scenario: Edit existing parafeerroute
- **GIVEN** the beheerder edits an existing parafeerroute that has no active voorstellen currently using it (no voorstel with status `in_parafering` referencing this route)
- **WHEN** the beheerder adds, removes, or reorders steps and saves
@@ -98,13 +97,13 @@ The system SHALL provide an admin UI for creating and managing parafeerroutes. R
---
-### REQ-PRE-004: Override Route on Specific Voorstel
+### Requirement: REQ-PRE-004 — Override Route on Specific Voorstel
The system SHALL allow authorized users (managers, secretariaat) to modify the parafeerroute on a specific in-flight voorstel: skip steps or add ad-hoc steps. A mandatory reason SHALL be recorded for skip actions. All overrides SHALL append entries to the voorstel's `auditTrail` and update the voorstel's `routeSnapshot`.
**Feature tier**: V1
-#### REQ-PRE-004-001: Skip a step
+#### Scenario: Skip a step
- **GIVEN** an authorized manager views a voorstel currently at step 2, and step 3 has `mandatory` = false ("Adviseur vakinhoud")
- **WHEN** the manager clicks "Stap overslaan" on step 3 and provides the reason: "Vakinhoudelijk advies niet vereist voor dit type omgevingsvergunning"
@@ -113,7 +112,7 @@ The system SHALL allow authorized users (managers, secretariaat) to modify the p
- **AND** the voorstel `auditTrail` SHALL include: "Stap overgeslagen: 'Adviseur vakinhoud' door [manager], reden: Vakinhoudelijk advies niet vereist voor dit type omgevingsvergunning"
- **AND** the skip action SHALL be blocked (with an error message) if the step has `mandatory` = true
-#### REQ-PRE-004-002: Add ad-hoc step
+#### Scenario: Add ad-hoc step
- **GIVEN** the steller adds an ad-hoc advisory step "Financieel adviseur" between step 2 and step 3 on a specific in-flight voorstel
- **WHEN** the steller confirms the insertion with actor UID and step type `advies`
diff --git a/openspec/changes/parafeerroute-engine/tasks.md b/openspec/changes/archive/2026-05-11-parafeerroute-engine/tasks.md
similarity index 100%
rename from openspec/changes/parafeerroute-engine/tasks.md
rename to openspec/changes/archive/2026-05-11-parafeerroute-engine/tasks.md
diff --git a/openspec/changes/parafering-actions/.openspec.yaml b/openspec/changes/archive/2026-05-11-parafering-actions/.openspec.yaml
similarity index 100%
rename from openspec/changes/parafering-actions/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-parafering-actions/.openspec.yaml
diff --git a/openspec/changes/parafering-actions/builds/build.json b/openspec/changes/archive/2026-05-11-parafering-actions/builds/build.json
similarity index 100%
rename from openspec/changes/parafering-actions/builds/build.json
rename to openspec/changes/archive/2026-05-11-parafering-actions/builds/build.json
diff --git a/openspec/changes/parafering-actions/context-brief.md b/openspec/changes/archive/2026-05-11-parafering-actions/context-brief.md
similarity index 100%
rename from openspec/changes/parafering-actions/context-brief.md
rename to openspec/changes/archive/2026-05-11-parafering-actions/context-brief.md
diff --git a/openspec/changes/parafering-actions/design.md b/openspec/changes/archive/2026-05-11-parafering-actions/design.md
similarity index 100%
rename from openspec/changes/parafering-actions/design.md
rename to openspec/changes/archive/2026-05-11-parafering-actions/design.md
diff --git a/openspec/changes/parafering-actions/hydra.json b/openspec/changes/archive/2026-05-11-parafering-actions/hydra.json
similarity index 100%
rename from openspec/changes/parafering-actions/hydra.json
rename to openspec/changes/archive/2026-05-11-parafering-actions/hydra.json
diff --git a/openspec/changes/parafering-actions/proposal.md b/openspec/changes/archive/2026-05-11-parafering-actions/proposal.md
similarity index 100%
rename from openspec/changes/parafering-actions/proposal.md
rename to openspec/changes/archive/2026-05-11-parafering-actions/proposal.md
diff --git a/openspec/changes/parafering-actions/specs/parafering-actions/spec.md b/openspec/changes/archive/2026-05-11-parafering-actions/specs/parafering-actions/spec.md
similarity index 92%
rename from openspec/changes/parafering-actions/specs/parafering-actions/spec.md
rename to openspec/changes/archive/2026-05-11-parafering-actions/specs/parafering-actions/spec.md
index fc07b0db..1160719c 100644
--- a/openspec/changes/parafering-actions/specs/parafering-actions/spec.md
+++ b/openspec/changes/archive/2026-05-11-parafering-actions/specs/parafering-actions/spec.md
@@ -11,17 +11,16 @@ Implement actor-facing parafering action recording and e-signature workflow with
The `parafeerroute-engine` change established the routing engine, admin configuration UI, and step activation (notifications + tasks). Actors receive Nextcloud tasks and notifications when their step is activated, but currently have no UI to record their decision. The `parafeeractie` entity (ADR-000) captures immutable action records — this change implements the service, controller, and frontend components that create and retrieve these records. The `onBehalfOf` and `mandate` fields in `parafeeractie` enable delegate workflows without custom RBAC. PDF signature annotation fulfils the contract lifecycle management and e-signature demand from the market (demand score 1901 + 921 + 258 across three related features).
-## Requirements
-
+## ADDED Requirements
---
-### REQ-PAA-001: Actor Action Taking
+### Requirement: REQ-PAA-001 — Actor Action Taking
The system SHALL provide an actor-facing action dialog for parafering steps. The dialog SHALL adapt its fields to the step type (`advies`, `parafering`, `accordering`). Only the actor assigned to the current step (or a valid delegate) SHALL be permitted to submit an action.
**Feature tier**: V1
-#### REQ-PAA-001-001: Advies step action
+#### Scenario: Advies step action
- **GIVEN** actor J. de Vries is assigned to step 1 (type: `advies`) of a voorstel currently at `currentStep` = 1
- **WHEN** J. de Vries opens the voorstel detail view and clicks "Actie nemen"
@@ -29,14 +28,14 @@ The system SHALL provide an actor-facing action dialog for parafering steps. The
- **AND** on submit, the system SHALL create a `parafeeractie` object: `step` = 1, `actor` = J. de Vries's UID, `action` = `advised`, `advice` = the entered text
- **AND** `ParafeerRouteService::completeStep` SHALL be called to advance `voorstel.currentStep` to 2 and activate the step 2 actor
-#### REQ-PAA-001-002: Parafering step action
+#### Scenario: Parafering step action
- **GIVEN** actor M. Bakker is the assigned actor for step 2 (type: `parafering`) and `currentStep` = 2
- **WHEN** M. Bakker opens the action dialog and optionally enters a comment, then clicks "Paraferen"
- **THEN** the system SHALL create a `parafeeractie`: `action` = `parafered`, `step` = 2, `actor` = M. Bakker's UID, `comment` = entered text (or null)
- **AND** the step SHALL advance to step 3
-#### REQ-PAA-001-003: Accordering step action
+#### Scenario: Accordering step action
- **GIVEN** actor H. van den Berg is assigned to the final step (type: `accordering`) and `currentStep` equals that step number
- **WHEN** H. van den Berg confirms in the action dialog and clicks "Accorderen"
@@ -45,7 +44,7 @@ The system SHALL provide an actor-facing action dialog for parafering steps. The
- **AND** the steller SHALL receive a Nextcloud notification: "Voorstel '[onderwerp]' is volledig geaccordeerd"
- **AND** a PDF signature annotation SHALL be applied to the voorstel document (see REQ-PAA-005)
-#### REQ-PAA-001-004: Unauthorized actor blocked
+#### Scenario: Unauthorized actor blocked
- **GIVEN** user P. Janssen is NOT the actor assigned to the current voorstel step
- **WHEN** P. Janssen attempts to `POST /api/parafeer-actie` for that voorstel
@@ -55,13 +54,13 @@ The system SHALL provide an actor-facing action dialog for parafering steps. The
---
-### REQ-PAA-002: Return for Revision
+### Requirement: REQ-PAA-002 — Return for Revision
The system SHALL allow the actor at the current step to return the voorstel to the steller for revision. A written reason SHALL be mandatory. The voorstel status SHALL transition to `teruggestuurd` and the steller SHALL be notified.
**Feature tier**: V1
-#### REQ-PAA-002-001: Return voorstel to steller
+#### Scenario: Return voorstel to steller
- **GIVEN** actor P. Janssen is at step 2 (type: `parafering`) of a voorstel with `onderwerp` = "Uitbreiding parkeerterrein Raadhuis"
- **WHEN** P. Janssen clicks "Terugsturen" in the action dialog, enters the reason "Financiële paragraaf ontbreekt. Kosten-batenanalyse toevoegen.", and confirms
@@ -71,7 +70,7 @@ The system SHALL allow the actor at the current step to return the voorstel to t
- **AND** the steller SHALL receive a Nextcloud notification: "Voorstel 'Uitbreiding parkeerterrein Raadhuis' is teruggestuurd door P. Janssen: Financiële paragraaf ontbreekt..."
- **AND** `voorstel.currentStep` SHALL remain at 2 (routing does not advance on return)
-#### REQ-PAA-002-002: Return reason is mandatory
+#### Scenario: Return reason is mandatory
- **GIVEN** actor P. Janssen opens the return dialog for a voorstel
- **WHEN** P. Janssen clicks "Terugsturen" without entering a reason
@@ -81,13 +80,13 @@ The system SHALL allow the actor at the current step to return the voorstel to t
---
-### REQ-PAA-003: Delegate / Mandaat Actions
+### Requirement: REQ-PAA-003 — Delegate / Mandaat Actions
The system SHALL allow actors to record a parafering action on behalf of a principal (mandaatgever) when a valid mandate is configured. The `parafeeractie` record SHALL store `actorType` = `delegate`, `onBehalfOf` = principal UID, and `mandate` = mandate reference.
**Feature tier**: V1
-#### REQ-PAA-003-001: Record action as delegate
+#### Scenario: Record action as delegate
- **GIVEN** S. de Vos has a configured mandate (reference "DT-002/2026") allowing them to act on behalf of K. Vermeulen (assigned actor for step 1)
- **WHEN** S. de Vos opens the action dialog, selects "Namens K. Vermeulen (mandaat DT-002/2026)" in the delegate selector, enters advice text, and submits
@@ -99,7 +98,7 @@ The system SHALL allow actors to record a parafering action on behalf of a princ
- `action` = `advised`
- **AND** the step SHALL advance as if K. Vermeulen had acted directly
-#### REQ-PAA-003-002: Delegate selector hidden when no mandates
+#### Scenario: Delegate selector hidden when no mandates
- **GIVEN** actor M. Bakker has no configured mandates
- **WHEN** M. Bakker opens the parafering action dialog
@@ -108,20 +107,20 @@ The system SHALL allow actors to record a parafering action on behalf of a princ
---
-### REQ-PAA-004: Action History Timeline
+### Requirement: REQ-PAA-004 — Action History Timeline
The system SHALL display a chronological read-only timeline of all `parafeeracties` for a voorstel, embedded in the voorstel detail view. Each entry SHALL show the actor, step number, action type, date/time, and any comment or advice text.
**Feature tier**: V1
-#### REQ-PAA-004-001: Timeline shows all recorded actions
+#### Scenario: Timeline shows all recorded actions
- **GIVEN** a voorstel has 3 completed `parafeeracties`: step 1 advised by J. de Vries, step 2 parafered by M. Bakker, step 2 returned by P. Janssen (on a separate voorstel)
- **WHEN** the steller opens the voorstel detail view
- **THEN** the `ParafeerActieTimeline` section SHALL display all `parafeeracties` for this voorstel in ascending chronological order
- **AND** each entry SHALL show: actor display name, step number, action label (e.g. "Geadviseerd", "Geparafeerd", "Teruggestuurd"), timestamp, and comment/advice (if present)
-#### REQ-PAA-004-002: Timeline accessible to all voorstel participants
+#### Scenario: Timeline accessible to all voorstel participants
- **GIVEN** the voorstel detail page is open
- **WHEN** any authenticated user with access to the case views the voorstel
@@ -130,13 +129,13 @@ The system SHALL display a chronological read-only timeline of all `parafeeracti
---
-### REQ-PAA-005: Digital Signature on Voorstel Document
+### Requirement: REQ-PAA-005 — Digital Signature on Voorstel Document
The system SHALL apply a PDF signature annotation to the voorstel's linked Nextcloud document when a step of type `accordering` is completed. The annotation SHALL record the actor UID, display name, timestamp, and step information. No external e-signature service is required for V1.
**Feature tier**: V1
-#### REQ-PAA-005-001: PDF signature applied on accordering
+#### Scenario: PDF signature applied on accordering
- **GIVEN** a voorstel has `document` = a Nextcloud file ID pointing to a PDF file, and the current step is of type `accordering`
- **WHEN** the actor completes the step with `action` = `accorded`
@@ -145,7 +144,7 @@ The system SHALL apply a PDF signature annotation to the voorstel's linked Nextc
- **AND** SHALL overwrite the existing Nextcloud file with the signed version
- **AND** the `parafeeractie` `comment` field SHALL record the signature metadata: "PDF handtekening aangebracht: [actor UID] op [timestamp]"
-#### REQ-PAA-005-002: Signing skipped when no document attached
+#### Scenario: Signing skipped when no document attached
- **GIVEN** a voorstel with `document` = null or empty (no document attached)
- **WHEN** an `accordering` step is completed
diff --git a/openspec/changes/parafering-actions/tasks.md b/openspec/changes/archive/2026-05-11-parafering-actions/tasks.md
similarity index 100%
rename from openspec/changes/parafering-actions/tasks.md
rename to openspec/changes/archive/2026-05-11-parafering-actions/tasks.md
diff --git a/openspec/changes/parafering-dashboard/.openspec.yaml b/openspec/changes/archive/2026-05-11-parafering-dashboard/.openspec.yaml
similarity index 100%
rename from openspec/changes/parafering-dashboard/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/.openspec.yaml
diff --git a/openspec/changes/parafering-dashboard/builds/build.json b/openspec/changes/archive/2026-05-11-parafering-dashboard/builds/build.json
similarity index 100%
rename from openspec/changes/parafering-dashboard/builds/build.json
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/builds/build.json
diff --git a/openspec/changes/parafering-dashboard/context-brief.md b/openspec/changes/archive/2026-05-11-parafering-dashboard/context-brief.md
similarity index 100%
rename from openspec/changes/parafering-dashboard/context-brief.md
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/context-brief.md
diff --git a/openspec/changes/parafering-dashboard/design.md b/openspec/changes/archive/2026-05-11-parafering-dashboard/design.md
similarity index 100%
rename from openspec/changes/parafering-dashboard/design.md
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/design.md
diff --git a/openspec/changes/parafering-dashboard/hydra.json b/openspec/changes/archive/2026-05-11-parafering-dashboard/hydra.json
similarity index 100%
rename from openspec/changes/parafering-dashboard/hydra.json
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/hydra.json
diff --git a/openspec/changes/parafering-dashboard/proposal.md b/openspec/changes/archive/2026-05-11-parafering-dashboard/proposal.md
similarity index 100%
rename from openspec/changes/parafering-dashboard/proposal.md
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/proposal.md
diff --git a/openspec/changes/parafering-dashboard/specs/parafering-dashboard/spec.md b/openspec/changes/archive/2026-05-11-parafering-dashboard/specs/parafering-dashboard/spec.md
similarity index 94%
rename from openspec/changes/parafering-dashboard/specs/parafering-dashboard/spec.md
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/specs/parafering-dashboard/spec.md
index 83483dd4..41ec309e 100644
--- a/openspec/changes/parafering-dashboard/specs/parafering-dashboard/spec.md
+++ b/openspec/changes/archive/2026-05-11-parafering-dashboard/specs/parafering-dashboard/spec.md
@@ -11,17 +11,16 @@ Implement a parafering dashboard and personal inbox for the Procest app. The sec
The `parafering-actions` change established the actor-facing action recording flow and `parafeerroute-engine` implemented the routing engine and step activation. Actors receive Nextcloud tasks when their step is activated and can record their decisions, but there is no centralized monitoring view. Secretariaat must open individual case detail pages to determine which voorstellen are stuck and who is responsible. This change implements the read-only dashboard surfaces and the reminder mechanism, consuming existing `voorstel` and `parafeeractie` data without introducing new entities.
-## Requirements
-
+## ADDED Requirements
---
-### REQ-PDB-001: Secretariaat Parafering Overview
+### Requirement: REQ-PDB-001 — Secretariaat Parafering Overview
The system SHALL provide a parafering dashboard at `/voorstellen` listing all active voorstellen with their current parafering status, intended for the secretariaat role. Voorstellen overdue on any step SHALL be highlighted. The list SHALL be sortable by multiple columns.
**Feature tier**: V1
-#### REQ-PDB-001-001: Overview with active voorstellen
+#### Scenario: Overview with active voorstellen
- **GIVEN** the secretariaat opens the parafering dashboard at `/voorstellen` and there are 8 active (`in_parafering`) voorstellen
- **WHEN** the page loads
@@ -29,7 +28,7 @@ The system SHALL provide a parafering dashboard at `/voorstellen` listing all ac
- **AND** voorstellen where days in current step exceeds the configured threshold SHALL be highlighted with a warning indicator (e.g., yellow badge or icon)
- **AND** the list SHALL be sortable by: onderwerp (alphabetical), status (alphabetical), days waiting (numeric), and steller (alphabetical) — clicking a column header toggles ascending/descending sort
-#### REQ-PDB-001-002: Empty dashboard
+#### Scenario: Empty dashboard
- **GIVEN** there are no voorstellen with `status` = `in_parafering`
- **WHEN** the secretariaat opens the parafering dashboard
@@ -39,13 +38,13 @@ The system SHALL provide a parafering dashboard at `/voorstellen` listing all ac
---
-### REQ-PDB-002: Personal Parafering Inbox
+### Requirement: REQ-PDB-002 — Personal Parafering Inbox
The system SHALL provide a personal parafering inbox showing voorstellen awaiting the current user's action. This SHALL be integrated into the MyWork view as a dedicated "Ter parafering" section. Each item SHALL be directly actionable without navigating to the full voorstel detail view.
**Feature tier**: V1
-#### REQ-PDB-002-001: View personal inbox
+#### Scenario: View personal inbox
- **GIVEN** wethouder Van Dam (UID: `wethouder.van.dam`) has 3 voorstellen where `routeSnapshot[currentStep-1].actor` = `wethouder.van.dam`
- **WHEN** Van Dam opens the MyWork view
@@ -53,7 +52,7 @@ The system SHALL provide a personal parafering inbox showing voorstellen awaitin
- **AND** each item SHALL display: onderwerp, case reference (derived from `voorstel.case`), steller (user display name), and waiting-since date (derived from the `createdAt` of the step activation `parafeeractie` or `voorstel.updatedAt`)
- **AND** each item SHALL show direct action buttons: "Paraferen" (or "Adviseren" / "Accorderen" depending on step type) and "Terugsturen", opening `ParafeerActieDialog` inline without navigating to the full voorstel detail view
-#### REQ-PDB-002-002: No pending parafering
+#### Scenario: No pending parafering
- **GIVEN** the current user has no voorstellen where they are the active step actor
- **WHEN** the user opens the MyWork view
@@ -62,13 +61,13 @@ The system SHALL provide a personal parafering inbox showing voorstellen awaitin
---
-### REQ-PDB-003: Send Parafering Reminder
+### Requirement: REQ-PDB-003 — Send Parafering Reminder
The system SHALL allow the secretariaat to send a reminder to the actor who has not yet acted on their parafering step. The reminder SHALL be a Nextcloud notification sent to the actor, and the action SHALL be logged in the parafering audit trail of the voorstel.
**Feature tier**: V1
-#### REQ-PDB-003-001: Send reminder to overdue actor
+#### Scenario: Send reminder to overdue actor
- **GIVEN** a voorstel "Omgevingsvergunning uitbreiding bedrijventerrein De Hoek" has been waiting at step "Afdelingshoofd parafeert" (actor: `p.janssen`) for 5 days, which exceeds the configured overdue threshold
- **AND** the secretariaat clicks "Herinnering sturen" on that voorstel's row
@@ -79,13 +78,13 @@ The system SHALL allow the secretariaat to send a reminder to the actor who has
---
-### REQ-PDB-004: Voorstel List Navigation
+### Requirement: REQ-PDB-004 — Voorstel List Navigation
The system SHALL add a "Voorstellen" navigation item to the Procest sidebar, linking to the parafering dashboard at `/voorstellen`. The item SHALL be visible to all authenticated Procest users.
**Feature tier**: V1
-#### REQ-PDB-004-001: Navigate to voorstellen
+#### Scenario: Navigate to voorstellen
- **GIVEN** an authenticated user is in the Procest app
- **WHEN** the user clicks "Voorstellen" in the Procest sidebar navigation
diff --git a/openspec/changes/parafering-dashboard/tasks.md b/openspec/changes/archive/2026-05-11-parafering-dashboard/tasks.md
similarity index 100%
rename from openspec/changes/parafering-dashboard/tasks.md
rename to openspec/changes/archive/2026-05-11-parafering-dashboard/tasks.md
diff --git a/openspec/changes/process-step-configuration/builds/build.json b/openspec/changes/archive/2026-05-11-process-step-configuration/builds/build.json
similarity index 100%
rename from openspec/changes/process-step-configuration/builds/build.json
rename to openspec/changes/archive/2026-05-11-process-step-configuration/builds/build.json
diff --git a/openspec/changes/process-step-configuration/design.md b/openspec/changes/archive/2026-05-11-process-step-configuration/design.md
similarity index 100%
rename from openspec/changes/process-step-configuration/design.md
rename to openspec/changes/archive/2026-05-11-process-step-configuration/design.md
diff --git a/openspec/changes/process-step-configuration/proposal.md b/openspec/changes/archive/2026-05-11-process-step-configuration/proposal.md
similarity index 100%
rename from openspec/changes/process-step-configuration/proposal.md
rename to openspec/changes/archive/2026-05-11-process-step-configuration/proposal.md
diff --git a/openspec/changes/process-step-configuration/specs/process-step-configuration/spec.md b/openspec/changes/archive/2026-05-11-process-step-configuration/specs/process-step-configuration/spec.md
similarity index 89%
rename from openspec/changes/process-step-configuration/specs/process-step-configuration/spec.md
rename to openspec/changes/archive/2026-05-11-process-step-configuration/specs/process-step-configuration/spec.md
index 304df4a6..99e6b42d 100644
--- a/openspec/changes/process-step-configuration/specs/process-step-configuration/spec.md
+++ b/openspec/changes/archive/2026-05-11-process-step-configuration/specs/process-step-configuration/spec.md
@@ -11,17 +11,16 @@ Extend the existing `process-step-configuration` capability (step CRUD inside a
`workflow-definition-model` (PR #347) introduced `WorkflowStep` as an embedded structural unit: ordered, status-bound, role-bound, optionally required, with a checklist. The behavioural axis — how long the step may take, which fields must be filled to complete it, what side-effects fire on completion, how to escalate when it runs over — is hard-coded today in `StatusTransitionService`, `CaseFieldValidator`, and the bezwaar-specific `BezwaarLifecycleService`. Tenants cannot tune these knobs without a developer release. This change exposes them as an additive `config` sub-object on every `WorkflowStep` and adds the validation, engine consumption, admin editor, and bezwaar backfill needed to put them into production. The shape is additive and backwards-compatible: existing seeded templates without `config` retain V1 behaviour exactly.
-## Requirements
-
+## ADDED Requirements
---
-### REQ-PSC-1: Step Config Sub-Object on WorkflowStep
+### Requirement: REQ-PSC-1 — Step Config Sub-Object on WorkflowStep
The system SHALL support an optional `config` sub-object on every embedded `WorkflowStep` inside `workflowTemplate.steps`. The shape is `{ sla?, requiredFields?, autoActions?, escalationRule? }`. All sub-fields SHALL be optional; absent `config` SHALL preserve the V1 behaviour of `workflow-definition-model` exactly.
**Feature tier**: V1
-#### REQ-PSC-1-001: Schema accepts step config
+#### Scenario: Schema accepts step config
- **GIVEN** the Procest app has run the latest repair step extending the `workflowTemplate` schema
- **WHEN** an administrator saves a `workflowTemplate` draft whose step "Toets ontvankelijkheid" carries `config: { sla: { value: 5, unit: "businessDays" }, requiredFields: ["isTimely"] }`
@@ -29,7 +28,7 @@ The system SHALL support an optional `config` sub-object on every embedded `Work
- **AND** reading the draft back SHALL return the `config` sub-object byte-for-byte
- **AND** other steps in the same draft that omit `config` SHALL remain unchanged
-#### REQ-PSC-1-002: Absent config preserves V1 behaviour
+#### Scenario: Absent config preserves V1 behaviour
- **GIVEN** a published `workflowTemplate` whose every step omits the `config` property
- **WHEN** a case in that workflow enters the status "In behandeling"
@@ -37,20 +36,20 @@ The system SHALL support an optional `config` sub-object on every embedded `Work
---
-### REQ-PSC-2: SLA on a Step
+### Requirement: REQ-PSC-2 — SLA on a Step
The system SHALL allow an SLA to be expressed per step as `{ value: positive integer, unit: enum("hours" | "businessDays" | "calendarDays") }`. When an SLA is present, the task creator SHALL stamp `task.dueAt` at step activation time and the status-transition engine SHALL surface the deadline on the case detail.
**Feature tier**: V1
-#### REQ-PSC-2-001: dueAt stamped from step SLA at activation
+#### Scenario: dueAt stamped from step SLA at activation
- **GIVEN** step "Inhoudelijke beoordeling" has `config.sla = { value: 10, unit: "businessDays" }`
- **WHEN** case "ZK-2026-0042" enters the status that owns this step at 2026-05-11 09:00 Amsterdam local time
- **THEN** the system SHALL create a task whose `dueAt` is 10 working days after activation (skipping weekends and Dutch public holidays per the existing platform calendar)
- **AND** the task detail UI SHALL render the deadline as "Verloopt op …"
-#### REQ-PSC-2-002: SLA value bounds enforced at publish
+#### Scenario: SLA value bounds enforced at publish
- **GIVEN** an administrator drafts a `workflowTemplate` containing a step with `config.sla.value = 0`
- **WHEN** the administrator attempts to publish the draft
@@ -59,13 +58,13 @@ The system SHALL allow an SLA to be expressed per step as `{ value: positive int
---
-### REQ-PSC-3: Required-Fields Guard from Step Config
+### Requirement: REQ-PSC-3 — Required-Fields Guard from Step Config
The system SHALL treat each entry of `config.requiredFields[]` on the case's current step as an implicit `requiredField` guard on every exit transition from that step's status. The user-facing block message SHALL match the existing transition-level `requiredField` guard format.
**Feature tier**: V1
-#### REQ-PSC-3-001: Missing required field blocks exit transition
+#### Scenario: Missing required field blocks exit transition
- **GIVEN** the case is in the status whose current step has `config.requiredFields = ["resultaat"]`
- **AND** the case has no value for the field "resultaat"
@@ -73,7 +72,7 @@ The system SHALL treat each entry of `config.requiredFields[]` on the case's cur
- **THEN** every exit transition button from that status SHALL be disabled
- **AND** the tooltip SHALL display `"Vereist veld ontbreekt: 'Resultaat'"` (label resolved via the linked caseType's field metadata)
-#### REQ-PSC-3-002: Filled required field unblocks exit transitions
+#### Scenario: Filled required field unblocks exit transitions
- **GIVEN** the same step and the case now has "resultaat" populated
- **WHEN** the handler views the case detail
@@ -82,13 +81,13 @@ The system SHALL treat each entry of `config.requiredFields[]` on the case's cur
---
-### REQ-PSC-4: StepConfigValidator at Publish
+### Requirement: REQ-PSC-4 — StepConfigValidator at Publish
The system SHALL validate every step's `config` at the moment of publishing a `workflowTemplate` draft. Validation SHALL be a pure-function check (no I/O) and SHALL reject malformed config without persisting it. Draft saves SHALL NOT trigger validation.
**Feature tier**: V1
-#### REQ-PSC-4-001: Unknown auto-action key refused
+#### Scenario: Unknown auto-action key refused
- **GIVEN** a draft `workflowTemplate` whose step has `config.autoActions = [{ key: "doesNotExist", parameters: {} }]`
- **WHEN** the administrator clicks "Publiceren"
@@ -97,19 +96,19 @@ The system SHALL validate every step's `config` at the moment of publishing a `w
- **AND** the response SHALL contain a generic static error string (no raw validator output)
- **AND** the structured error SHALL be logged via `logger->warning()`
-#### REQ-PSC-4-002: Dangling required-field reference refused
+#### Scenario: Dangling required-field reference refused
- **GIVEN** a draft step references `config.requiredFields = ["thisFieldDoesNotExist"]` and the linked caseType has no such property
- **WHEN** the administrator clicks "Publiceren"
- **THEN** the publish SHALL be refused with logged error `code: unknown_field_reference`
-#### REQ-PSC-4-003: Escalation without SLA refused
+#### Scenario: Escalation without SLA refused
- **GIVEN** a draft step with `config.escalationRule` present but `config.sla` absent
- **WHEN** the administrator clicks "Publiceren"
- **THEN** the publish SHALL be refused with logged error `code: escalation_requires_sla`
-#### REQ-PSC-4-004: Draft saves bypass validation
+#### Scenario: Draft saves bypass validation
- **GIVEN** the same malformed draft from REQ-PSC-4-001
- **WHEN** the administrator clicks "Opslaan" (draft save) instead of "Publiceren"
@@ -118,20 +117,20 @@ The system SHALL validate every step's `config` at the moment of publishing a `w
---
-### REQ-PSC-5: Step Auto-Actions on Completion
+### Requirement: REQ-PSC-5 — Step Auto-Actions on Completion
The system SHALL execute `config.autoActions[]` on the current step when the step is completed. Step-level auto-actions SHALL fire BEFORE transition-level auto-actions when the completion event also drives a transition. Failures of individual auto-actions SHALL be logged but SHALL NOT roll back the step completion.
**Feature tier**: V1
-#### REQ-PSC-5-001: Step auto-actions fire on completion
+#### Scenario: Step auto-actions fire on completion
- **GIVEN** step "Advies opstellen" has `config.autoActions = [{ key: "notifySteller", parameters: {} }, { key: "logToAuditTrail", parameters: { event: "advice_complete" } }]`
- **WHEN** the handler marks the step complete
- **THEN** the platform SHALL invoke "notifySteller" first, then "logToAuditTrail"
- **AND** both actions SHALL fire BEFORE any transition-level `automaticActions` if the completion also triggers a transition
-#### REQ-PSC-5-002: Auto-action failure does not roll back step completion
+#### Scenario: Auto-action failure does not roll back step completion
- **GIVEN** the same step where "notifySteller" raises an exception at runtime
- **WHEN** the handler marks the step complete
@@ -141,13 +140,13 @@ The system SHALL execute `config.autoActions[]` on the current step when the ste
---
-### REQ-PSC-6: Escalation Rule on a Step
+### Requirement: REQ-PSC-6 — Escalation Rule on a Step
The system SHALL support a per-step `escalationRule` declaring when and how to escalate when the step approaches or breaches its SLA. The rule SHALL include `trigger` (`preBreach` or `slaBreached`), `offset` + `offsetUnit`, `notifyRole`, `escalateToRole`, and `openIncident`. The task creator SHALL stamp `task.escalationAt` at activation time; a downstream runner (owned by `automatic-actions`) drives the actual side-effects.
**Feature tier**: V1
-#### REQ-PSC-6-001: escalationAt stamped on activation for preBreach trigger
+#### Scenario: escalationAt stamped on activation for preBreach trigger
- **GIVEN** step "Bezwaar afhandelen" has `config.sla = { value: 42, unit: "calendarDays" }` and `config.escalationRule = { trigger: "preBreach", offset: 5, offsetUnit: "calendarDays", notifyRole: "Behandelaar", escalateToRole: "Afdelingshoofd", openIncident: false }`
- **WHEN** the case enters the status owning this step on 2026-05-11
@@ -155,7 +154,7 @@ The system SHALL support a per-step `escalationRule` declaring when and how to e
- **AND** the task SHALL have `escalationAt = 2026-06-17` (5 calendar days before `dueAt`)
- **AND** the task SHALL persist the `notifyRole`, `escalateToRole`, and `openIncident` values for the downstream runner
-#### REQ-PSC-6-002: escalationAt for slaBreached trigger lands after dueAt
+#### Scenario: escalationAt for slaBreached trigger lands after dueAt
- **GIVEN** the same step but with `escalationRule.trigger = "slaBreached"` and `offset = 2, offsetUnit = "businessDays"`
- **WHEN** the case enters the status on 2026-05-11 (a Monday)
@@ -163,27 +162,27 @@ The system SHALL support a per-step `escalationRule` declaring when and how to e
---
-### REQ-PSC-7: Admin Editor Panel per Step
+### Requirement: REQ-PSC-7 — Admin Editor Panel per Step
The system SHALL surface step configuration as a collapsible "Geavanceerd" panel rendered inside the existing per-step row of `WorkflowStepsEditor`. The panel SHALL be writable only on `draft` definitions and SHALL render read-only on `published` or `deprecated` definitions. No separate route or dialog SHALL be introduced.
**Feature tier**: V1
-#### REQ-PSC-7-001: Editor visible only on draft
+#### Scenario: Editor visible only on draft
- **GIVEN** the administrator opens a `workflowTemplate` in lifecycle status `draft`
- **WHEN** the administrator expands a step row and clicks "Geavanceerd"
- **THEN** the SLA / required-fields / auto-actions / escalation inputs SHALL all be editable
- **AND** saving the draft SHALL persist the new `config` block without invoking the validator
-#### REQ-PSC-7-002: Editor read-only on published
+#### Scenario: Editor read-only on published
- **GIVEN** the administrator opens the same template after publishing it
- **WHEN** the administrator expands a step row and clicks "Geavanceerd"
- **THEN** every input SHALL render in a disabled state
- **AND** the panel SHALL display a banner: "Gepubliceerde versies zijn niet bewerkbaar — kloon eerst een nieuwe versie."
-#### REQ-PSC-7-003: Editor strings localised
+#### Scenario: Editor strings localised
- **GIVEN** the administrator's Nextcloud locale is `nl_NL`
- **WHEN** the panel renders
@@ -191,13 +190,13 @@ The system SHALL surface step configuration as a collapsible "Geavanceerd" panel
---
-### REQ-PSC-8: Backfill Bezwaar Step Config
+### Requirement: REQ-PSC-8 — Backfill Bezwaar Step Config
The system SHALL provide a repair step that migrates the hard-coded SLAs and required-field lists from `BezwaarLifecycleService` into `config` on the seeded bezwaar `workflowTemplate`. The migration SHALL respect `workflow-definition-model` immutability: it SHALL clone the existing published version, attach the `config` blocks, publish the new version, and deprecate the previous version. Open cases pinned to the previous version SHALL be unaffected.
**Feature tier**: V1
-#### REQ-PSC-8-001: Seeded bezwaar workflow gets config blocks
+#### Scenario: Seeded bezwaar workflow gets config blocks
- **GIVEN** the Procest app has the seeded bezwaar `workflowTemplate` at `version: 1, lifecycleStatus: published` with hard-coded SLAs in `BezwaarLifecycleService`
- **WHEN** the repair step runs
@@ -206,14 +205,14 @@ The system SHALL provide a repair step that migrates the hard-coded SLAs and req
- **AND** version 1 SHALL be moved to `lifecycleStatus: deprecated, isActive: false`
- **AND** `caseType.workflowDefinition` for the Bezwaar case type SHALL point at version 2
-#### REQ-PSC-8-002: Open cases stay pinned to the prior version
+#### Scenario: Open cases stay pinned to the prior version
- **GIVEN** three open bezwaar cases were created against version 1 (with `case.workflowVersion = 1`)
- **WHEN** the repair step publishes version 2
- **THEN** the three open cases SHALL still resolve their definition to version 1 via `WorkflowDefinitionService::getDefinitionForCase()`
- **AND** their behaviour SHALL be unchanged (no new SLAs retroactively applied)
-#### REQ-PSC-8-003: Repair step is idempotent
+#### Scenario: Repair step is idempotent
- **GIVEN** the repair step has already run once (version 2 with `config` is active)
- **WHEN** the repair step runs a second time
diff --git a/openspec/changes/process-step-configuration/tasks.md b/openspec/changes/archive/2026-05-11-process-step-configuration/tasks.md
similarity index 100%
rename from openspec/changes/process-step-configuration/tasks.md
rename to openspec/changes/archive/2026-05-11-process-step-configuration/tasks.md
diff --git a/openspec/changes/procest-adopt-or-abstractions/builds/build.json b/openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/builds/build.json
similarity index 100%
rename from openspec/changes/procest-adopt-or-abstractions/builds/build.json
rename to openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/builds/build.json
diff --git a/openspec/changes/procest-adopt-or-abstractions/design.md b/openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/design.md
similarity index 100%
rename from openspec/changes/procest-adopt-or-abstractions/design.md
rename to openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/design.md
diff --git a/openspec/changes/procest-adopt-or-abstractions/proposal.md b/openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/proposal.md
similarity index 100%
rename from openspec/changes/procest-adopt-or-abstractions/proposal.md
rename to openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/proposal.md
diff --git a/openspec/changes/procest-adopt-or-abstractions/specs/procest-adopt-or-abstractions/spec.md b/openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/specs/procest-adopt-or-abstractions/spec.md
similarity index 87%
rename from openspec/changes/procest-adopt-or-abstractions/specs/procest-adopt-or-abstractions/spec.md
rename to openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/specs/procest-adopt-or-abstractions/spec.md
index c72006c5..be63b1e1 100644
--- a/openspec/changes/procest-adopt-or-abstractions/specs/procest-adopt-or-abstractions/spec.md
+++ b/openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/specs/procest-adopt-or-abstractions/spec.md
@@ -180,6 +180,8 @@ metadata on cases.
### Requirement: Procest SHALL adopt the OR i18n source of truth
+The system SHALL satisfy the behaviour described as "Procest SHALL adopt the OR i18n source of truth".
+
All user-facing case strings — state labels, transition action labels,
notification subjects/bodies, dashboard widget titles — SHALL be
sourced through OR's `register-i18n` capability, with Dutch and English
@@ -232,6 +234,8 @@ dependencies, consumed capabilities, and provided capabilities.
### Requirement: ZGW protocol bindings SHALL remain app-local
+The system SHALL satisfy the behaviour described as "ZGW protocol bindings SHALL remain app-local".
+
`lib/Service/NotificatieService.php`, `lib/Controller/ZrcController.php`,
`lib/Controller/BrcController.php`, `lib/Controller/ZtcController.php`,
`lib/Controller/NrcController.php`, `lib/Service/ZgwZtcRulesService.php`,
@@ -259,6 +263,8 @@ to VNG ZGW protocol — published Dutch government standards.
### Requirement: Procest SHALL adopt OR's register-resolver service
+The system SHALL satisfy the behaviour described as "Procest SHALL adopt OR's register-resolver service".
+
When OR's `register-resolver-service` capability lands (per
`04-hardcoded.md:136–138`), procest SHALL replace direct
`getValueString(APP_ID, 'foo_register', '')` calls with
@@ -276,6 +282,8 @@ OR with per-tenant override semantics (per ADR-022).
### Requirement: Procest SHALL consume nextcloud-vue multi-tenancy context
+The system SHALL satisfy the behaviour described as "Procest SHALL consume nextcloud-vue multi-tenancy context".
+
Per `feedback_design-system-cd-first.md`-style alignment with the
nextcloud-vue contract, procest's case Vue components SHALL consume
the `multi-tenancy-context` composable so that store calls and
@@ -305,6 +313,8 @@ this requirement formalises it as the contract and adds the
### Requirement: Case-management spec is the canonical lifecycle home
+The system SHALL satisfy the behaviour described as "Case-management spec is the canonical lifecycle home".
+
The previously distributed lifecycle requirements (across
`case-management`, `parafering-actions`, `parafering-audit-trail`,
`parafeerroute-engine`) are consolidated into a single
@@ -331,6 +341,8 @@ closing notes.
### Requirement: Bezwaar-lifecycle spec uses x-openregister-calculations
+The system SHALL satisfy the behaviour described as "Bezwaar-lifecycle spec uses x-openregister-calculations".
+
`openspec/specs/bezwaar-lifecycle/spec.md:88,95` is updated so the AWB
deadline calculation requirement is declarative
(`x-openregister-calculations`) rather than implying a
@@ -346,6 +358,8 @@ deadline calculation requirement is declarative
### Requirement: Voorstel-management spec retro-documents the lifecycle
+The system SHALL satisfy the behaviour described as "Voorstel-management spec retro-documents the lifecycle".
+
`openspec/specs/voorstel-management/spec.md` is updated to retro-
document the consolidated lifecycle annotation rather than describing
a custom paraferingflow.
@@ -360,16 +374,26 @@ a custom paraferingflow.
### Requirement: Automatic-actions spec uses lifecycle annotation
+The system SHALL satisfy the behaviour described as "Automatic-actions spec uses lifecycle annotation".
+
`openspec/specs/automatic-actions/spec.md` is updated so per-status
branching is expressed as lifecycle transitions rather than a
per-status PHP service.
**Rationale:** Audit ref: `04-hardcoded.md:98`.
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
## REMOVED Requirements
### Requirement: Custom parafering state machine in PHP
+The system SHALL satisfy the behaviour described as "Custom parafering state machine in PHP".
+
**Removed.** The previous requirement (implied by
`ParaferingService.php:43-353` and described across four specs) for a
hand-maintained PHP state machine with `STATUS_*` constants and
@@ -379,8 +403,16 @@ guarded transition methods is removed. Replaced by ADDED
**Rationale:** Audit ref: `02-spec-rewrite.md:64,66,67;
04-hardcoded.md:85–86`.
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
### Requirement: Custom parafering audit trail
+The system SHALL satisfy the behaviour described as "Custom parafering audit trail".
+
**Removed.** The previous requirement for a bespoke parafeeractie
versioning table is removed. Replaced by consumption of OR's
`audit-trail-immutable` capability with delegation tracking
@@ -389,24 +421,48 @@ versioning table is removed. Replaced by consumption of OR's
**Rationale:** Audit ref: `02-spec-rewrite.md:65;
01-code-cleanup.md`.
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
### Requirement: Custom parafeerroute engine
+The system SHALL satisfy the behaviour described as "Custom parafeerroute engine".
+
**Removed.** The previous requirement for a bespoke route-modification
engine is removed. Replaced by lifecycle transitions with `requires`
guards encoding skip-step / ad-hoc-step rules.
**Rationale:** Audit ref: `02-spec-rewrite.md:67`.
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
### Requirement: Custom case-status transition tables
+The system SHALL satisfy the behaviour described as "Custom case-status transition tables".
+
**Removed.** The previous requirement for `CaseStatus` and
`CaseTransition` database tables is removed. State and transitions
are declared as schema annotation; OR provides the engine.
**Rationale:** Audit ref: `02-spec-rewrite.md:64`.
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
### Requirement: Hand-rolled parafering notification service
+The system SHALL satisfy the behaviour described as "Hand-rolled parafering notification service".
+
**Removed.** The previous requirement implied by
`ParaferingNotificationService.php` for three hand-rolled
`notificationManager->createNotification()` calls per transition is
@@ -414,14 +470,28 @@ removed. Replaced by `x-openregister-notifications` rules.
**Rationale:** Audit ref: `04-hardcoded.md:87`.
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
### Requirement: PHP-built case email subject
+The system SHALL satisfy the behaviour described as "PHP-built case email subject".
+
**Removed.** The previous requirement implied by
`CaseEmailService.php:92` for a PHP-built `setSubject()` call is
removed. Replaced by schema-declared notification copy via i18n keys.
**Rationale:** Audit ref: `04-hardcoded.md:93`.
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
## Deferred to follow-up implementation change
The following code-side moves are referenced by this spec but are NOT
diff --git a/openspec/changes/procest-adopt-or-abstractions/tasks.md b/openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/tasks.md
similarity index 100%
rename from openspec/changes/procest-adopt-or-abstractions/tasks.md
rename to openspec/changes/archive/2026-05-11-procest-adopt-or-abstractions/tasks.md
diff --git a/openspec/changes/procest-manifest-v1/builds/build.json b/openspec/changes/archive/2026-05-11-procest-manifest-v1/builds/build.json
similarity index 100%
rename from openspec/changes/procest-manifest-v1/builds/build.json
rename to openspec/changes/archive/2026-05-11-procest-manifest-v1/builds/build.json
diff --git a/openspec/changes/procest-manifest-v1/design.md b/openspec/changes/archive/2026-05-11-procest-manifest-v1/design.md
similarity index 100%
rename from openspec/changes/procest-manifest-v1/design.md
rename to openspec/changes/archive/2026-05-11-procest-manifest-v1/design.md
diff --git a/openspec/changes/procest-manifest-v1/proposal.md b/openspec/changes/archive/2026-05-11-procest-manifest-v1/proposal.md
similarity index 100%
rename from openspec/changes/procest-manifest-v1/proposal.md
rename to openspec/changes/archive/2026-05-11-procest-manifest-v1/proposal.md
diff --git a/openspec/changes/procest-manifest-v1/specs/procest-manifest-v1/spec.md b/openspec/changes/archive/2026-05-11-procest-manifest-v1/specs/procest-manifest-v1/spec.md
similarity index 62%
rename from openspec/changes/procest-manifest-v1/specs/procest-manifest-v1/spec.md
rename to openspec/changes/archive/2026-05-11-procest-manifest-v1/specs/procest-manifest-v1/spec.md
index 879027b2..76c8332f 100644
--- a/openspec/changes/procest-manifest-v1/specs/procest-manifest-v1/spec.md
+++ b/openspec/changes/archive/2026-05-11-procest-manifest-v1/specs/procest-manifest-v1/spec.md
@@ -2,7 +2,7 @@
## ADDED Requirements
-### REQ-PMV1-1: Manifest validates against schema v1.2.0
+### Requirement: REQ-PMV1-1 — Manifest validates against schema v1.2.0
`procest/src/manifest.json` MUST validate against
`node_modules/@conduction/nextcloud-vue/src/schemas/app-manifest.schema.json`
@@ -12,11 +12,13 @@ top-level `version` field MUST be `"1.0.0"`. The top-level
#### Scenario: Validator script returns success
-- WHEN the developer runs `node tests/validate-manifest.js`
-- THEN the script exits with code `0`
-- AND prints `[validate-manifest] Ajv validation: PASS (0 errors)`
+- **WHEN** the developer runs `node tests/validate-manifest.js`
+- **THEN** the script exits with code `0`
+- **AND** prints `[validate-manifest] Ajv validation: PASS (0 errors)`
-### REQ-PMV1-2: All current router routes are present in the manifest
+### Requirement: REQ-PMV1-2 — All current router routes are present in the manifest
+
+The system SHALL satisfy the behaviour described as "REQ-PMV1-2 — All current router routes are present in the manifest".
Every route in the original `src/router/index.js` (`Dashboard`,
`Doorlooptijd`, `MyWork`, `Werkvoorraad`, `Cases`, `CaseDetail`,
@@ -28,11 +30,11 @@ MUST match the original router's path (so existing
#### Scenario: Existing route names resolve
-- GIVEN any of the legacy route names listed above
-- WHEN the renderer builds the vue-router config from the manifest
-- THEN a route with that `name` exists with the same path
+- **GIVEN** any of the legacy route names listed above
+- **WHEN** the renderer builds the vue-router config from the manifest
+- **THEN** a route with that `name` exists with the same path
-### REQ-PMV1-3: customComponents registry holds documented exceptions only
+### Requirement: REQ-PMV1-3 — customComponents registry holds documented exceptions only
`src/customComponents.js` MUST export exactly the entries listed in
`design.md`'s "Surviving" section: `MyWorkView`, `WerkvoorraadView`,
@@ -45,12 +47,12 @@ new entry MUST require an updated row in the design doc's
#### Scenario: Each entry has a documented justification
-- GIVEN any export from `customComponents.js`
-- WHEN a reviewer cross-references `design.md`
-- THEN the entry appears in either the Custom-fallback inventory or the
+- **GIVEN** any export from `customComponents.js`
+- **WHEN** a reviewer cross-references `design.md`
+- **THEN** the entry appears in either the Custom-fallback inventory or the
Sidebar-tab inventory with a one-line justification
-### REQ-PMV1-4: Mount-survivable bootstrap
+### Requirement: REQ-PMV1-4 — Mount-survivable bootstrap
`src/main.js` MUST NOT block Vue mount on translation loading. The
boot sequence MUST:
@@ -68,12 +70,12 @@ through Apache.
#### Scenario: Translation 404 does not crash boot
-- GIVEN the dev container that returns 404 for `/custom_apps/procest/l10n/en.json`
-- WHEN the user loads the app
-- THEN the Vue shell mounts and renders English source strings
-- AND no uncaught promise rejection appears in the browser console
+- **GIVEN** the dev container that returns 404 for `/custom_apps/procest/l10n/en.json`
+- **WHEN** the user loads the app
+- **THEN** the Vue shell mounts and renders English source strings
+- **AND** no uncaught promise rejection appears in the browser console
-### REQ-PMV1-5: Vue.extend frozen-component workaround
+### Requirement: REQ-PMV1-5 — Vue.extend frozen-component workaround
`src/main.js` MUST shallow-clone `CnPageRenderer` (and the
`defaultPageTypes` / `customComponents` registry maps) before passing
@@ -84,11 +86,11 @@ extensible" against non-extensible barrel exports.
#### Scenario: Router loads without throwing
-- WHEN the app boots
-- THEN no `_Ctor` extensibility error appears in the browser console
-- AND every route's component resolves to a `CnPageRenderer` clone
+- **WHEN** the app boots
+- **THEN** no `_Ctor` extensibility error appears in the browser console
+- **AND** every route's component resolves to a `CnPageRenderer` clone
-### REQ-PMV1-6: en_US locale alias mirrors en
+### Requirement: REQ-PMV1-6 — en_US locale alias mirrors en
`l10n/en_US.json` MUST exist and be byte-identical to `l10n/en.json`.
This prevents the `en_US`-locale `loadTranslations` request from
@@ -98,11 +100,11 @@ production installs).
#### Scenario: en_US users get English strings
-- GIVEN a user with browser locale `en_US`
-- WHEN they load procest
-- THEN every `t('procest', '...')` call returns the English source
+- **GIVEN** a user with browser locale `en_US`
+- **WHEN** they load procest
+- **THEN** every `t('procest', '...')` call returns the English source
-### REQ-PMV1-7: @nextcloud/axios pin via overrides
+### Requirement: REQ-PMV1-7 — @nextcloud/axios pin via overrides
`package.json` MUST keep `@nextcloud/axios` pinned at `~2.5.2` via the
top-level `overrides` block. Version 2.5.2 still declares both `import`
@@ -113,11 +115,11 @@ Mirrors decidesk's working webpack/package.json setup.
#### Scenario: Production build succeeds
-- WHEN the developer runs `NODE_ENV=production npx webpack`
-- THEN the build exits with code `0`
-- AND no `not exported under conditions` errors appear in the output
+- **WHEN** the developer runs `NODE_ENV=production npx webpack`
+- **THEN** the build exits with code `0`
+- **AND** no `not exported under conditions` errors appear in the output
-### REQ-PMV1-8: Library version pin
+### Requirement: REQ-PMV1-8 — Library version pin
`package.json` MUST declare `@conduction/nextcloud-vue` at `^1.0.0-beta.12`
or higher. This pin guarantees the published `Vue.extend` frozen-component
@@ -127,12 +129,12 @@ are present.
#### Scenario: npm install resolves a compatible version
-- GIVEN `package.json` declares `^1.0.0-beta.12`
-- WHEN `npm install --legacy-peer-deps` runs
-- THEN `node_modules/@conduction/nextcloud-vue/package.json` reports a
+- **GIVEN** `package.json` declares `^1.0.0-beta.12`
+- **WHEN** `npm install --legacy-peer-deps` runs
+- **THEN** `node_modules/@conduction/nextcloud-vue/package.json` reports a
version `>= 1.0.0-beta.12`
-### REQ-PMV1-9: Catch-all redirect preserved
+### Requirement: REQ-PMV1-9 — Catch-all redirect preserved
The renderer MUST add a catch-all route `*` redirecting to `/` after
the manifest pages. This preserves the original router's
@@ -141,11 +143,11 @@ returns the user to the dashboard).
#### Scenario: Unknown route redirects
-- GIVEN a user visits `/apps/procest/some-nonexistent-path`
-- WHEN vue-router resolves the path
-- THEN they are redirected to `/`
+- **GIVEN** a user visits `/apps/procest/some-nonexistent-path`
+- **WHEN** vue-router resolves the path
+- **THEN** they are redirected to `/`
-### REQ-PMV1-10: appinfo version bump
+### Requirement: REQ-PMV1-10 — appinfo version bump
`appinfo/info.xml` `` MUST be bumped to `0.2.0` (minor bump
marking the manifest migration). The Nextcloud app store reads this
@@ -154,6 +156,6 @@ shells.
#### Scenario: info.xml reports new version
-- GIVEN this change is applied
-- WHEN a reviewer reads `appinfo/info.xml`
-- THEN the `` element contains `0.2.0`
+- **GIVEN** this change is applied
+- **WHEN** a reviewer reads `appinfo/info.xml`
+- **THEN** the `` element contains `0.2.0`
diff --git a/openspec/changes/procest-manifest-v1/tasks.md b/openspec/changes/archive/2026-05-11-procest-manifest-v1/tasks.md
similarity index 100%
rename from openspec/changes/procest-manifest-v1/tasks.md
rename to openspec/changes/archive/2026-05-11-procest-manifest-v1/tasks.md
diff --git a/openspec/changes/procest-store-migration/builds/build.json b/openspec/changes/archive/2026-05-11-procest-store-migration/builds/build.json
similarity index 100%
rename from openspec/changes/procest-store-migration/builds/build.json
rename to openspec/changes/archive/2026-05-11-procest-store-migration/builds/build.json
diff --git a/openspec/changes/procest-store-migration/design.md b/openspec/changes/archive/2026-05-11-procest-store-migration/design.md
similarity index 100%
rename from openspec/changes/procest-store-migration/design.md
rename to openspec/changes/archive/2026-05-11-procest-store-migration/design.md
diff --git a/openspec/changes/procest-store-migration/proposal.md b/openspec/changes/archive/2026-05-11-procest-store-migration/proposal.md
similarity index 100%
rename from openspec/changes/procest-store-migration/proposal.md
rename to openspec/changes/archive/2026-05-11-procest-store-migration/proposal.md
diff --git a/openspec/changes/procest-store-migration/specs/procest-store-migration/spec.md b/openspec/changes/archive/2026-05-11-procest-store-migration/specs/procest-store-migration/spec.md
similarity index 96%
rename from openspec/changes/procest-store-migration/specs/procest-store-migration/spec.md
rename to openspec/changes/archive/2026-05-11-procest-store-migration/specs/procest-store-migration/spec.md
index 4e8a75e2..2c0ee6ef 100644
--- a/openspec/changes/procest-store-migration/specs/procest-store-migration/spec.md
+++ b/openspec/changes/archive/2026-05-11-procest-store-migration/specs/procest-store-migration/spec.md
@@ -37,6 +37,8 @@ All Pinia store call sites in procest that operate on OpenRegister objects MUST
### Requirement: Procest-specific config stores MAY remain as plain Pinia `defineStore`s
+The system SHALL satisfy the behaviour described as "Procest-specific config stores MAY remain as plain Pinia `defineStore`s".
+
Procest carries config endpoints (`/apps/procest/api/settings`, `/apps/procest/api/zgw-mappings`) that are not OpenRegister objects. These stores are out of scope for the lib's `useObjectStore`.
#### Scenario: A store wraps a procest-specific REST endpoint
diff --git a/openspec/changes/procest-store-migration/tasks.md b/openspec/changes/archive/2026-05-11-procest-store-migration/tasks.md
similarity index 100%
rename from openspec/changes/procest-store-migration/tasks.md
rename to openspec/changes/archive/2026-05-11-procest-store-migration/tasks.md
diff --git a/openspec/changes/prometheus-metrics/builds/build.json b/openspec/changes/archive/2026-05-11-prometheus-metrics/builds/build.json
similarity index 100%
rename from openspec/changes/prometheus-metrics/builds/build.json
rename to openspec/changes/archive/2026-05-11-prometheus-metrics/builds/build.json
diff --git a/openspec/changes/prometheus-metrics/design.md b/openspec/changes/archive/2026-05-11-prometheus-metrics/design.md
similarity index 100%
rename from openspec/changes/prometheus-metrics/design.md
rename to openspec/changes/archive/2026-05-11-prometheus-metrics/design.md
diff --git a/openspec/changes/prometheus-metrics/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-prometheus-metrics/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/prometheus-metrics/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-prometheus-metrics/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/prometheus-metrics/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-prometheus-metrics/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/prometheus-metrics/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-prometheus-metrics/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/prometheus-metrics/proposal.md b/openspec/changes/archive/2026-05-11-prometheus-metrics/proposal.md
similarity index 100%
rename from openspec/changes/prometheus-metrics/proposal.md
rename to openspec/changes/archive/2026-05-11-prometheus-metrics/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-prometheus-metrics/specs/prometheus-metrics/spec.md b/openspec/changes/archive/2026-05-11-prometheus-metrics/specs/prometheus-metrics/spec.md
new file mode 100644
index 00000000..71deb355
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-prometheus-metrics/specs/prometheus-metrics/spec.md
@@ -0,0 +1,61 @@
+# Delta: prometheus-metrics
+
+## ADDED Requirements
+### Requirement: REQ-PROM — 002a (ENHANCED)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- Added `nextcloud_version` label to `procest_info` gauge
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-PROM — 002b (ENHANCED)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- `procest_up` gauge now reflects actual database health (was hardcoded to 1)
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-PROM — 003e (IMPLEMENTED)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- Added `procest_cases_created_today` gauge metric
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-PROM — 004d (IMPLEMENTED)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- Added OpenRegister dependency check to health endpoint
+- OpenRegister unavailable sets overall status to "error"
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-PROM — 009a/b (IMPLEMENTED)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- Added APCu caching for all expensive metric queries
+- Case counts and task counts cached for 30 seconds
+- Overdue counts cached for 60 seconds
+- Graceful fallback when APCu is unavailable
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
diff --git a/openspec/changes/prometheus-metrics/tasks.md b/openspec/changes/archive/2026-05-11-prometheus-metrics/tasks.md
similarity index 100%
rename from openspec/changes/prometheus-metrics/tasks.md
rename to openspec/changes/archive/2026-05-11-prometheus-metrics/tasks.md
diff --git a/openspec/changes/register-i18n/.openspec.yaml b/openspec/changes/archive/2026-05-11-register-i18n/.openspec.yaml
similarity index 100%
rename from openspec/changes/register-i18n/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-register-i18n/.openspec.yaml
diff --git a/openspec/changes/register-i18n/builds/build.json b/openspec/changes/archive/2026-05-11-register-i18n/builds/build.json
similarity index 100%
rename from openspec/changes/register-i18n/builds/build.json
rename to openspec/changes/archive/2026-05-11-register-i18n/builds/build.json
diff --git a/openspec/changes/register-i18n/design.md b/openspec/changes/archive/2026-05-11-register-i18n/design.md
similarity index 100%
rename from openspec/changes/register-i18n/design.md
rename to openspec/changes/archive/2026-05-11-register-i18n/design.md
diff --git a/openspec/changes/register-i18n/proposal.md b/openspec/changes/archive/2026-05-11-register-i18n/proposal.md
similarity index 100%
rename from openspec/changes/register-i18n/proposal.md
rename to openspec/changes/archive/2026-05-11-register-i18n/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-register-i18n/specs/register-i18n/spec.md b/openspec/changes/archive/2026-05-11-register-i18n/specs/register-i18n/spec.md
new file mode 100644
index 00000000..2b120dec
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-register-i18n/specs/register-i18n/spec.md
@@ -0,0 +1,47 @@
+# Delta: register-i18n
+
+## ADDED Requirements
+### Requirement: REQ-I18N — 001c/d (IMPLEMENTED - schema flags)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- Added `x-translatable: true` to translatable properties in procest_register.json
+- caseType: title, description, purpose, trigger, subject
+- statusType: name, description
+- resultType: name, description
+- roleType: name, description
+- documentType: name, description
+- decisionType: name, description
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-I18N — 003a (IMPLEMENTED - frontend utility)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- Created `src/utils/i18nResolver.js` with:
+ - `resolveTranslatable(value, locale, fallbackLocale)` — resolves language-tagged fields with fallback chain
+ - `getUserLocale()` — gets user's locale from Nextcloud
+ - `resolveField(obj, field, locale)` — convenience wrapper
+ - `resolveText(obj, field, locale)` — template-friendly wrapper
+- Fallback chain: user locale -> fallback -> nl -> en -> first available
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
+
+### Requirement: REQ-I18N — 008a/b/c (IMPLEMENTED - glossary)
+The system SHALL satisfy the behaviour described as "requirement".
+
+- Added 22 glossary terms to l10n/en.json and l10n/nl.json
+- Covers: entity types, status labels, action buttons per the spec glossary tables
+
+#### Scenario: behaviour satisfied
+
+- **GIVEN** the system is configured per this requirement
+- **WHEN** the described trigger occurs
+- **THEN** the system SHALL exhibit the documented behaviour
diff --git a/openspec/changes/register-i18n/tasks.md b/openspec/changes/archive/2026-05-11-register-i18n/tasks.md
similarity index 100%
rename from openspec/changes/register-i18n/tasks.md
rename to openspec/changes/archive/2026-05-11-register-i18n/tasks.md
diff --git a/openspec/changes/roles-decisions/.openspec.yaml b/openspec/changes/archive/2026-05-11-roles-decisions/.openspec.yaml
similarity index 100%
rename from openspec/changes/roles-decisions/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-roles-decisions/.openspec.yaml
diff --git a/openspec/changes/roles-decisions/builds/build.json b/openspec/changes/archive/2026-05-11-roles-decisions/builds/build.json
similarity index 100%
rename from openspec/changes/roles-decisions/builds/build.json
rename to openspec/changes/archive/2026-05-11-roles-decisions/builds/build.json
diff --git a/openspec/changes/roles-decisions/design.md b/openspec/changes/archive/2026-05-11-roles-decisions/design.md
similarity index 100%
rename from openspec/changes/roles-decisions/design.md
rename to openspec/changes/archive/2026-05-11-roles-decisions/design.md
diff --git a/openspec/changes/roles-decisions/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/roles-decisions/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/roles-decisions/pipeline-logs/build.2.jsonl.gz b/openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.2.jsonl.gz
similarity index 100%
rename from openspec/changes/roles-decisions/pipeline-logs/build.2.jsonl.gz
rename to openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.2.jsonl.gz
diff --git a/openspec/changes/roles-decisions/pipeline-logs/build.3.jsonl.gz b/openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.3.jsonl.gz
similarity index 100%
rename from openspec/changes/roles-decisions/pipeline-logs/build.3.jsonl.gz
rename to openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.3.jsonl.gz
diff --git a/openspec/changes/roles-decisions/pipeline-logs/build.4.jsonl.gz b/openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.4.jsonl.gz
similarity index 100%
rename from openspec/changes/roles-decisions/pipeline-logs/build.4.jsonl.gz
rename to openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.4.jsonl.gz
diff --git a/openspec/changes/roles-decisions/pipeline-logs/build.5.jsonl.gz b/openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.5.jsonl.gz
similarity index 100%
rename from openspec/changes/roles-decisions/pipeline-logs/build.5.jsonl.gz
rename to openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.5.jsonl.gz
diff --git a/openspec/changes/roles-decisions/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/roles-decisions/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-roles-decisions/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/roles-decisions/proposal.md b/openspec/changes/archive/2026-05-11-roles-decisions/proposal.md
similarity index 100%
rename from openspec/changes/roles-decisions/proposal.md
rename to openspec/changes/archive/2026-05-11-roles-decisions/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-roles-decisions/specs/roles-decisions/spec.md b/openspec/changes/archive/2026-05-11-roles-decisions/specs/roles-decisions/spec.md
new file mode 100644
index 00000000..b7927a37
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-roles-decisions/specs/roles-decisions/spec.md
@@ -0,0 +1,88 @@
+---
+status: proposed
+---
+
+# roles-decisions Specification (Change Delta)
+
+## Purpose
+Deliver the case-detail UI gaps for Roles & Decisions: a dedicated Decisions section with CRUD and validity-period display, archival-metadata-aware Result section, and field-level role validation. The canonical capability spec lives at `openspec/specs/roles-decisions/spec.md`; this delta narrows scope to the missing UI affordances and the role/decision linkage rules enforced in the frontend.
+
+## ADDED Requirements
+### Requirement: REQ-DECISION-001 Decisions section MUST support full CRUD from case detail
+The case-detail view SHALL render a `DecisionsSection.vue` component that lists decisions linked to the current case and supports create, edit, and delete operations subject to permissions.
+
+#### Scenario: Authorized role creates a decision
+- **GIVEN** a case worker with role `handler` or `decision_maker` opens case `ZAAK-2026-000123`
+- **WHEN** they click "Besluit toevoegen" in the Decisions section
+- **THEN** a dialog MUST collect `title`, `description`, `decisionType`, `decidedBy`, `decidedAt`, `effectiveDate`, and `expiryDate`
+- **AND** on save the decision object MUST be persisted via OpenRegister with `case` set to `ZAAK-2026-000123`
+- **AND** the new decision MUST appear in the section list without a full page reload
+
+#### Scenario: Unauthorized role cannot create a decision
+- **GIVEN** a user whose only role on the case is `stakeholder`
+- **WHEN** the case detail loads
+- **THEN** the "Besluit toevoegen" action MUST be hidden
+- **AND** the section MUST be read-only
+
+### Requirement: REQ-DECISION-002 Decision validity period MUST be displayed and computed
+Each decision row SHALL show its validity window based on `effectiveDate` and `expiryDate`, computed in `decisionHelpers.js`.
+
+#### Scenario: Decision is currently valid
+- **GIVEN** a decision with `effectiveDate` 2026-01-01 and `expiryDate` 2026-12-31
+- **WHEN** the current date is 2026-05-11
+- **THEN** the row MUST render a green "Geldig" badge with text "Geldig tot 31-12-2026"
+
+#### Scenario: Decision has expired
+- **GIVEN** a decision with `expiryDate` 2026-04-01
+- **WHEN** the current date is 2026-05-11
+- **THEN** the row MUST render a grey "Verlopen" badge with the expiry date
+
+#### Scenario: Decision has no expiry
+- **GIVEN** a decision with `effectiveDate` set and `expiryDate` empty
+- **THEN** the badge MUST read "Geldig (onbepaalde tijd)"
+
+### Requirement: REQ-DECISION-005 Decisions section MUST be present on every case detail view
+The Decisions section SHALL appear on `CaseDetail.vue` between the Result section and the activity timeline regardless of whether decisions exist.
+
+#### Scenario: Case has no decisions
+- **GIVEN** case `ZAAK-2026-000123` has zero decisions
+- **THEN** the section MUST render its header and an empty-state hint "Nog geen besluiten genomen"
+- **AND** the create action MUST remain available to authorized roles
+
+### Requirement: REQ-RESULT-001 Result section MUST display archival metadata
+`ResultSection.vue` SHALL surface the archival action and retention information derived from the linked `resultType`.
+
+#### Scenario: Result with retention rule
+- **GIVEN** the case has a result whose `resultType` has `archiveAction` `retain` and `retentionPeriod` `P20Y`
+- **THEN** the section MUST display: "Bewaartermijn: 20 jaar", "Archiefactie: bewaren", and the computed retention end date based on `retentionDateSource`
+
+#### Scenario: Result with destroy action
+- **GIVEN** `archiveAction` is `destroy` with `retentionPeriod` `P5Y`
+- **THEN** the section MUST display "Archiefactie: vernietigen" with a tooltip explaining the disposal date
+
+### Requirement: REQ-ROLE-006 Role validation MUST enforce field requirements per role type
+When creating or editing a Role on a case, the form SHALL enforce required fields defined by the linked `roleType.genericRole`.
+
+#### Scenario: Initiator requires participant
+- **GIVEN** the user selects `roleType` whose `genericRole` is `initiator`
+- **WHEN** they leave `participant` empty and submit
+- **THEN** the form MUST block submission with error "Een initiator vereist een deelnemer"
+
+#### Scenario: Decision-maker role gates decision creation
+- **GIVEN** no user holds a role with `genericRole` `decision_maker` on the case
+- **WHEN** any user attempts to open the "Besluit toevoegen" dialog
+- **THEN** the dialog MUST display a warning "Geen besluitnemer toegewezen op deze zaak"
+- **AND** the save action MUST be disabled until a decision_maker role is assigned
+
+### Requirement: REQ-ROLE-007 Role-to-decision audit trail MUST be preserved
+Every decision write SHALL record the actor's role at the moment of the action, so the audit history reflects which role authorized the decision even if the user's roles change later.
+
+#### Scenario: Audit trail on decision create
+- **GIVEN** user `alice` holds role `decision_maker` on case `ZAAK-2026-000123`
+- **WHEN** alice creates a decision
+- **THEN** the decision's audit entry MUST contain `actor: alice`, `actorRole: decision_maker`, and the timestamp
+- **AND** a later removal of alice's `decision_maker` role MUST NOT alter the historical audit entry
+
+#### Scenario: Audit trail on decision reversal
+- **GIVEN** an existing decision is deleted by a `coordinator` role
+- **THEN** the audit entry MUST record `action: delete`, `actor`, `actorRole: coordinator`, and the original decision payload for traceability
diff --git a/openspec/changes/roles-decisions/tasks.md b/openspec/changes/archive/2026-05-11-roles-decisions/tasks.md
similarity index 100%
rename from openspec/changes/roles-decisions/tasks.md
rename to openspec/changes/archive/2026-05-11-roles-decisions/tasks.md
diff --git a/openspec/changes/status-transition-engine/builds/build.json b/openspec/changes/archive/2026-05-11-status-transition-engine/builds/build.json
similarity index 100%
rename from openspec/changes/status-transition-engine/builds/build.json
rename to openspec/changes/archive/2026-05-11-status-transition-engine/builds/build.json
diff --git a/openspec/changes/status-transition-engine/design.md b/openspec/changes/archive/2026-05-11-status-transition-engine/design.md
similarity index 100%
rename from openspec/changes/status-transition-engine/design.md
rename to openspec/changes/archive/2026-05-11-status-transition-engine/design.md
diff --git a/openspec/changes/status-transition-engine/hydra.json b/openspec/changes/archive/2026-05-11-status-transition-engine/hydra.json
similarity index 100%
rename from openspec/changes/status-transition-engine/hydra.json
rename to openspec/changes/archive/2026-05-11-status-transition-engine/hydra.json
diff --git a/openspec/changes/status-transition-engine/proposal.md b/openspec/changes/archive/2026-05-11-status-transition-engine/proposal.md
similarity index 100%
rename from openspec/changes/status-transition-engine/proposal.md
rename to openspec/changes/archive/2026-05-11-status-transition-engine/proposal.md
diff --git a/openspec/changes/status-transition-engine/specs/status-transition-engine/spec.md b/openspec/changes/archive/2026-05-11-status-transition-engine/specs/status-transition-engine/spec.md
similarity index 89%
rename from openspec/changes/status-transition-engine/specs/status-transition-engine/spec.md
rename to openspec/changes/archive/2026-05-11-status-transition-engine/specs/status-transition-engine/spec.md
index c5f77509..5aefe0cd 100644
--- a/openspec/changes/status-transition-engine/specs/status-transition-engine/spec.md
+++ b/openspec/changes/archive/2026-05-11-status-transition-engine/specs/status-transition-engine/spec.md
@@ -11,17 +11,16 @@ Implement the runtime engine that drives every Procest case through its statuses
The `workflow-definition-model` spec defined the data shape of workflow templates: ordered steps, transitions with `fromStatus`/`toStatus`/`label`/`guards`/`automaticActions`/`allowedRoles`. Today no Procest service consumes those transitions at runtime — status changes happen through scattered hand-coded paths in `ZgwZrcRulesService` and `ZrcController`, with no guard enforcement, no automatic actions, and inconsistent `statusRecord` writes. This change closes that gap by introducing a single engine that turns workflow-definition data into deterministic runtime behaviour, surfaced uniformly to UI, REST, and the future visual workflow editor.
-## Requirements
-
+## ADDED Requirements
---
-### REQ-STE-1: Transition Rule Consumption
+### Requirement: REQ-STE-1 — Transition Rule Consumption
The system SHALL load the active `workflowTemplate` for a case's `caseType` and parse its `transitions[]` JSON into runtime-usable rule objects, indexed by `fromStatus`. Only the workflow template with `isActive: true` for the given `caseType` SHALL be consulted; draft and inactive templates SHALL be ignored at runtime.
**Feature tier**: V1
-#### REQ-STE-1-001: Load active workflow template
+#### Scenario: Load active workflow template
- **GIVEN** a `caseType` with two `workflowTemplate` objects: `version 1, isActive: false` and `version 2, isActive: true, isDraft: false`
- **WHEN** the engine is asked for available transitions on a case of that type
@@ -29,7 +28,7 @@ The system SHALL load the active `workflowTemplate` for a case's `caseType` and
- **AND** the engine SHALL decode `transitions` and `steps` from JSON
- **AND** `version 1`'s transitions SHALL NOT be considered
-#### REQ-STE-1-002: No active template falls through to free-form
+#### Scenario: No active template falls through to free-form
- **GIVEN** a `caseType` with no `workflowTemplate` where `isActive: true`
- **WHEN** the engine is asked for available transitions
@@ -38,13 +37,13 @@ The system SHALL load the active `workflowTemplate` for a case's `caseType` and
---
-### REQ-STE-2: Guard Registry and Evaluation
+### Requirement: REQ-STE-2 — Guard Registry and Evaluation
The system SHALL evaluate every guard declared on a transition before allowing the transition to proceed. Guard types `checklist`, `requiredField`, `requiredDocument`, and `roleGuard` SHALL be supported in V1. Guards combine conjunctively: ALL guards must pass for the transition to be available. Guard evaluation SHALL be deterministic and side-effect-free.
**Feature tier**: V1
-#### REQ-STE-2-001: All guards pass
+#### Scenario: All guards pass
- **GIVEN** a transition with one `requiredField` guard on `resultaat` and one `roleGuard` allowing `Behandelaar`
- **AND** the case has `resultaat: "Toegekend"`
@@ -53,7 +52,7 @@ The system SHALL evaluate every guard declared on a transition before allowing t
- **THEN** the transition SHALL be reported as `guardsPassed: true`
- **AND** `failedGuards` SHALL be empty
-#### REQ-STE-2-002: Single guard fails — transition unavailable
+#### Scenario: Single guard fails — transition unavailable
- **GIVEN** a transition with a `checklist` guard requiring 5 items completed
- **AND** the case has only 4 of 5 items checked
@@ -61,7 +60,7 @@ The system SHALL evaluate every guard declared on a transition before allowing t
- **THEN** the transition SHALL be reported as `guardsPassed: false`
- **AND** `failedGuards` SHALL contain one entry: `{type: 'checklist', failureMessage: "1 checklistitem niet afgevinkt: 'Besluit opgesteld'"}`
-#### REQ-STE-2-003: Role guard hides transition
+#### Scenario: Role guard hides transition
- **GIVEN** a transition restricted to role `Afdelingshoofd`
- **AND** the current user has role `Behandelaar`
@@ -69,7 +68,7 @@ The system SHALL evaluate every guard declared on a transition before allowing t
- **THEN** the transition SHALL NOT appear in the returned list
- **AND** the failure SHALL be recorded with `details.silent: true` so the UI does not render a disabled button for it
-#### REQ-STE-2-004: Required document guard
+#### Scenario: Required document guard
- **GIVEN** a transition with a `requiredDocument` guard for type `Besluit`
- **AND** the case has no document of type `Besluit` attached
@@ -79,20 +78,20 @@ The system SHALL evaluate every guard declared on a transition before allowing t
---
-### REQ-STE-3: Available Transitions Computation
+### Requirement: REQ-STE-3 — Available Transitions Computation
The system SHALL compute, for any given case and user, the set of transitions whose `fromStatus` matches the case's current `status`, whose `roleGuard` admits the user, and whose remaining guards (besides RoleGuard) are evaluated and reported with pass/fail breakdown.
**Feature tier**: V1
-#### REQ-STE-3-001: List available transitions on case detail
+#### Scenario: List available transitions on case detail
- **GIVEN** a case in status `In behandeling` for a caseType whose active workflow defines two transitions from `In behandeling`: `Goedkeuren` (RoleGuard: `Afdelingshoofd`) and `Terugsturen` (no role guard)
- **WHEN** a user with role `Behandelaar` opens the case detail
- **THEN** the API SHALL return `transitions: [{id: , label: "Terugsturen", toStatus: , guardsPassed: true, failedGuards: []}]`
- **AND** `Goedkeuren` SHALL NOT appear in the response
-#### REQ-STE-3-002: Final status — no transitions
+#### Scenario: Final status — no transitions
- **GIVEN** a case in a status with `isFinal: true`
- **WHEN** any user requests available transitions
@@ -101,13 +100,13 @@ The system SHALL compute, for any given case and user, the set of transitions wh
---
-### REQ-STE-4: Atomic Transition Execution
+### Requirement: REQ-STE-4 — Atomic Transition Execution
The system SHALL execute status transitions atomically: case status update, `statusRecord` creation, and `case.auditTrail` append happen as a single logical operation. Guard re-evaluation SHALL occur server-side as defence in depth; UI guard checks SHALL NOT be trusted.
**Feature tier**: V1
-#### REQ-STE-4-001: Successful transition writes a statusRecord
+#### Scenario: Successful transition writes a statusRecord
- **GIVEN** a case in status `In behandeling`
- **WHEN** the case handler executes transition `Afronden` to `Afgehandeld` via `POST /api/case/{id}/transition`
@@ -116,7 +115,7 @@ The system SHALL execute status transitions atomically: case status update, `sta
- **AND** `case.updatedAt` SHALL be refreshed
- **AND** the response SHALL be `200` with `{status: 'ok', statusRecord, dispatchedActions}`
-#### REQ-STE-4-002: Server-side guard re-evaluation blocks stale UI
+#### Scenario: Server-side guard re-evaluation blocks stale UI
- **GIVEN** the UI shows a transition as available (cached)
- **AND** server-side a guard has since failed (e.g. a required document was removed)
@@ -128,13 +127,13 @@ The system SHALL execute status transitions atomically: case status update, `sta
---
-### REQ-STE-5: Side-Effect Dispatching
+### Requirement: REQ-STE-5 — Side-Effect Dispatching
The system SHALL dispatch all `automaticActions[]` configured on a successful transition. Action types `sendEmail`, `createTask`, `createSubCase`, `webhook`, `setField`, and `notify` SHALL be supported in V1. Actions SHALL fire **sequentially in declaration order**. Failure of any individual action SHALL be logged with full context but SHALL NOT roll back the status change.
**Feature tier**: V1
-#### REQ-STE-5-001: Transition triggers automatic actions
+#### Scenario: Transition triggers automatic actions
- **GIVEN** transition `Goedkeuren` is configured with two actions: `sendEmail` then `createTask`
- **WHEN** the transition is successfully executed
@@ -142,7 +141,7 @@ The system SHALL dispatch all `automaticActions[]` configured on a successful tr
- **AND** both handlers SHALL receive the full transition context (`fromStatus`, `toStatus`, `transitionLabel`, `userId`, `statusRecordUuid`)
- **AND** the resulting `statusRecord.dispatchedActions` SHALL be `[{type: 'sendEmail', ok: true}, {type: 'createTask', ok: true}]`
-#### REQ-STE-5-002: Failed action does not roll back
+#### Scenario: Failed action does not roll back
- **GIVEN** transition `Afronden` has a `webhook` action and a `setField` action
- **WHEN** the webhook target returns HTTP 500
@@ -152,7 +151,7 @@ The system SHALL dispatch all `automaticActions[]` configured on a successful tr
- **AND** the error SHALL be logged via `$this->logger->error()` with full context
- **AND** the case owner SHALL receive an in-app notification: "Een automatische actie op zaak is mislukt"
-#### REQ-STE-5-003: Unknown action type is logged but does not abort
+#### Scenario: Unknown action type is logged but does not abort
- **GIVEN** a transition includes an action with `type: "publishToLinkedIn"` for which no handler is registered
- **WHEN** the engine dispatches actions
@@ -162,13 +161,13 @@ The system SHALL dispatch all `automaticActions[]` configured on a successful tr
---
-### REQ-STE-6: Transition Audit Log and Replay
+### Requirement: REQ-STE-6 — Transition Audit Log and Replay
The system SHALL persist every transition as a `statusRecord` linked to the case. The full chronological history of a case's transitions SHALL be replayable via a single API call. Replay SHALL NOT re-fire side-effects.
**Feature tier**: V1
-#### REQ-STE-6-001: Replay chronological history
+#### Scenario: Replay chronological history
- **GIVEN** a case has been through three transitions: `Ontvangen → In behandeling → Afgehandeld`
- **WHEN** a user requests `GET /api/case/{id}/transition-history`
@@ -179,13 +178,13 @@ The system SHALL persist every transition as a `statusRecord` linked to the case
---
-### REQ-STE-7: REST Controller Surface
+### Requirement: REQ-STE-7 — REST Controller Surface
The system SHALL expose REST endpoints for available-transition queries, transition execution, admin free-form transitions, and history replay. ALL error responses SHALL use static failure messages. The controller SHALL NEVER return `$e->getMessage()` in a response body.
**Feature tier**: V1
-#### REQ-STE-7-001: Endpoint table
+#### Scenario: Endpoint table
- **GIVEN** the engine routes are registered
- **WHEN** the routes are inspected
@@ -195,7 +194,7 @@ The system SHALL expose REST endpoints for available-transition queries, transit
- `POST /api/case/{caseId}/transition-freeform` (admin only; body `{toStatusId, comment?}`)
- `GET /api/case/{caseId}/transition-history`
-#### REQ-STE-7-002: Authorisation enforcement
+#### Scenario: Authorisation enforcement
- **GIVEN** a user without the procest admin group
- **WHEN** the user calls `POST /api/case/{caseId}/transition-freeform`
@@ -204,13 +203,13 @@ The system SHALL expose REST endpoints for available-transition queries, transit
---
-### REQ-STE-8: Backfill of Legacy Status Logic
+### Requirement: REQ-STE-8 — Backfill of Legacy Status Logic
The system SHALL retire status mutation paths in `ZgwZrcRulesService` and `ZrcController`, delegating instead to the engine. Legacy ZGW API contracts SHALL be preserved: `POST /zaken/v1/statussen` continues to validate `zrc-016` (statustype belongs to zaaktype) and writes a status, but the write SHALL now flow through `StatusTransitionService::execute` (or `executeFreeForm`) so that a `statusRecord` is always emitted.
**Feature tier**: V1
-#### REQ-STE-8-001: Eindstatus side-effects migrate to action handlers
+#### Scenario: Eindstatus side-effects migrate to action handlers
- **GIVEN** a workflow template's eindstatus transition is configured with `setField` actions for `einddatum: now()` and `result: ` (replacing the inline `handleEindstatusEffect` logic)
- **WHEN** the transition fires
@@ -218,7 +217,7 @@ The system SHALL retire status mutation paths in `ZgwZrcRulesService` and `ZrcCo
- **AND** the legacy `ZrcController::handleEindstatusEffect` method SHALL be removed
- **AND** existing ZGW Newman regression tests SHALL pass unchanged
-#### REQ-STE-8-002: ZGW statussen POST emits a statusRecord
+#### Scenario: ZGW statussen POST emits a statusRecord
- **GIVEN** a client posts `POST /zaken/v1/statussen` with valid body
- **WHEN** the request passes `rulesStatussenCreate` validation
@@ -228,20 +227,20 @@ The system SHALL retire status mutation paths in `ZgwZrcRulesService` and `ZrcCo
---
-### REQ-STE-9: Integration Hooks for Downstream Specs
+### Requirement: REQ-STE-9 — Integration Hooks for Downstream Specs
The system SHALL allow downstream specs (`bezwaar-lifecycle`, `parafeerroute-engine`) to plug into transition execution by registering side-effect handlers through DI service tagging, without modifying engine source. The dispatcher SHALL be entity-aware: in addition to `case.status` transitions, it SHALL accept transitions on the configured set of typed entities (V1 includes `case`; the extension point reserves the typed-entity dispatch path for parafering's `voorstel.status` flow).
**Feature tier**: V1
-#### REQ-STE-9-001: bezwaar-lifecycle registers createSubCase handler
+#### Scenario: bezwaar-lifecycle registers createSubCase handler
- **GIVEN** the bezwaar workflow template's `Ontvankelijk → In behandeling` transition lists a `createSubCase` action
- **WHEN** the transition fires
- **THEN** the engine SHALL invoke the `CreateSubCaseHandler` registered by the `bezwaar-lifecycle` integration
- **AND** an advisory deelzaak SHALL be created linked to the primary bezwaar case via `hoofdzaak`
-#### REQ-STE-9-002: parafeerroute-engine uses the same dispatcher
+#### Scenario: parafeerroute-engine uses the same dispatcher
- **GIVEN** a `voorstel.status` change to `in_parafering` configured to emit a `notify` action
- **WHEN** parafeerroute-engine triggers the typed-entity transition path
@@ -250,13 +249,13 @@ The system SHALL allow downstream specs (`bezwaar-lifecycle`, `parafeerroute-eng
---
-### REQ-STE-10: No-Workflow Fallback for Free-Form Transitions
+### Requirement: REQ-STE-10 — No-Workflow Fallback for Free-Form Transitions
The system SHALL allow procest admins to transition cases whose `caseType` lacks an active workflow template to any non-final `statusType` of that caseType, with full audit logging. Free-form transitions SHALL bypass guard evaluation and side-effect dispatching but SHALL still write a `statusRecord` flagged `noWorkflowTemplate: true`.
**Feature tier**: V1
-#### REQ-STE-10-001: Admin free-form transition
+#### Scenario: Admin free-form transition
- **GIVEN** a case of a caseType with no `workflowTemplate` where `isActive: true`
- **AND** the current user is in the procest admin group
@@ -267,7 +266,7 @@ The system SHALL allow procest admins to transition cases whose `caseType` lacks
- **AND** a `statusRecord` SHALL be written with `noWorkflowTemplate: true`, `evaluatedGuards: []`, `dispatchedActions: []`
- **AND** no side-effects SHALL fire
-#### REQ-STE-10-002: Non-admin cannot free-form
+#### Scenario: Non-admin cannot free-form
- **GIVEN** a case of a caseType with no active workflow template
- **AND** the current user is NOT in the procest admin group
diff --git a/openspec/changes/status-transition-engine/tasks.md b/openspec/changes/archive/2026-05-11-status-transition-engine/tasks.md
similarity index 100%
rename from openspec/changes/status-transition-engine/tasks.md
rename to openspec/changes/archive/2026-05-11-status-transition-engine/tasks.md
diff --git a/openspec/changes/stuf-support/.openspec.yaml b/openspec/changes/archive/2026-05-11-stuf-support/.openspec.yaml
similarity index 100%
rename from openspec/changes/stuf-support/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-stuf-support/.openspec.yaml
diff --git a/openspec/changes/stuf-support/builds/build.json b/openspec/changes/archive/2026-05-11-stuf-support/builds/build.json
similarity index 100%
rename from openspec/changes/stuf-support/builds/build.json
rename to openspec/changes/archive/2026-05-11-stuf-support/builds/build.json
diff --git a/openspec/changes/stuf-support/design.md b/openspec/changes/archive/2026-05-11-stuf-support/design.md
similarity index 100%
rename from openspec/changes/stuf-support/design.md
rename to openspec/changes/archive/2026-05-11-stuf-support/design.md
diff --git a/openspec/changes/stuf-support/proposal.md b/openspec/changes/archive/2026-05-11-stuf-support/proposal.md
similarity index 100%
rename from openspec/changes/stuf-support/proposal.md
rename to openspec/changes/archive/2026-05-11-stuf-support/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-stuf-support/specs/stuf-support/spec.md b/openspec/changes/archive/2026-05-11-stuf-support/specs/stuf-support/spec.md
new file mode 100644
index 00000000..9cd82563
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-stuf-support/specs/stuf-support/spec.md
@@ -0,0 +1,876 @@
+---
+status: implemented
+---
+# StUF Protocol Support Specification
+
+## Purpose
+
+Procest currently implements ZGW APIs (Zaken, Catalogi, Documenten, Besluiten) for case management via REST controllers (`ZrcController`, `ZtcController`, `DrcController`, `BrcController`). However, many Dutch municipalities still rely on StUF (Standaard Uitwisseling Formaat) -- especially StUF-ZKN (Zaak-Kennis) for case management and StUF-BG (Basis Gemeentelijke) for person/address lookups. This spec defines how Procest supports StUF alongside ZGW, providing a dual API surface over the same OpenRegister case data.
+
+StUF support enables Procest to integrate with legacy form systems (e.g., formulierenmotoren that submit cases via StUF-ZKN), legacy case systems during migration periods, and BRP person lookups via StUF-BG. The approach leverages OpenConnector's existing SOAP infrastructure (SOAPService with StUF-ZKN `edcLk01` awareness) for outbound StUF calls, while adding inbound StUF endpoints to Procest for receiving SOAP messages from legacy consumers.
+
+**Standards**: StUF 3.01, StUF-ZKN 3.10, StUF-BG 3.10, ZGW APIs (VNG), RGBZ, GEMMA
+**Feature tier**: V1 (outbound StUF via OpenConnector), V2 (inbound StUF endpoints in Procest)
+
+---
+
+## Architecture Overview
+
+```
+ Inbound StUF (V2)
+ Legacy systems send SOAP messages to Procest
+ ┌──────────────────────────────────┐
+ │ Legacy Formulierensysteem │
+ │ Legacy Zaaksysteem │
+ └────────────┬─────────────────────┘
+ │ SOAP (StUF-ZKN/BG)
+ ┌────────────▼─────────────────────┐
+ │ Procest StUF Controller │
+ │ - Raw XML POST handler │
+ │ - StUF message parser │
+ │ - StUF → OpenRegister mapper │
+ └────────────┬─────────────────────┘
+ │ Internal API
+┌──────────────────────────────────────▼──────────────────────┐
+│ OpenRegister (procest register) │
+│ - case, caseType, status, role, result, decision schemas │
+│ - Single source of truth for all case data │
+└──────────────────────────────────────▲──────────────────────┘
+ │ Internal API
+ ┌────────────┴─────────────────────┐
+ │ OpenConnector (SOAP outbound) │
+ │ - SOAPService (existing) │
+ │ - StUF-ZKN/BG message builder │
+ │ - mTLS / WS-Security auth │
+ └────────────┬─────────────────────┘
+ │ SOAP (StUF-ZKN/BG)
+ ┌────────────▼─────────────────────┐
+ │ External Legacy Services │
+ │ - BRP (StUF-BG personen) │
+ │ - Legacy zaaksysteem │
+ │ - Gemeentelijk DMS │
+ └──────────────────────────────────┘
+ Outbound StUF (V1)
+ Procest/OpenConnector queries legacy systems
+```
+
+### Coexistence Principle
+
+StUF and ZGW share the same underlying OpenRegister data. A case created via StUF-ZKN `zakLk01` is stored in the same `case` schema and is immediately visible via the ZGW Zaken API (`ZrcController`) and the Procest frontend. Conversely, a case created via the ZGW API or UI can be queried via StUF-ZKN `zakLv01`. The translation layer maps between the two representations without duplicating data.
+
+---
+
+## Data Model Mapping
+
+### StUF-ZKN to Procest Case Mapping
+
+| StUF-ZKN Field | XML Path | Procest Case Property | ZGW Mapping | Notes |
+|----------------|----------|----------------------|-------------|-------|
+| `zaakidentificatie` | `zakLk01/object/identificatie` | `identifier` | `identificatie` | Auto-generated if not provided |
+| `omschrijving` | `zakLk01/object/omschrijving` | `title` | `omschrijving` | Required |
+| `toelichting` | `zakLk01/object/toelichting` | `description` | `toelichting` | Optional |
+| `startdatum` | `zakLk01/object/startdatum` | `startDate` | `startdatum` | Format: `YYYYMMDD` -> ISO 8601 |
+| `einddatum` | `zakLk01/object/einddatum` | `endDate` | `einddatum` | Format: `YYYYMMDD` -> ISO 8601 |
+| `einddatumGepland` | `zakLk01/object/einddatumGepland` | `plannedEndDate` | `einddatumGepland` | |
+| `uiterlijkeEinddatumAfdoening` | `zakLk01/object/uiterlijkeEinddatumAfdoening` | `deadline` | `uiterlijkeEinddatumAfdoening` | |
+| `isVan/gerelateerde/code` | Nested element | `caseType` (resolved by code) | `zaaktype` | Resolved via caseType lookup |
+| `vertrouwelijkAanduiding` | `zakLk01/object/vertrouwelijkAanduiding` | `confidentiality` | `vertrouwelijkheidaanduiding` | Enum mapping required |
+| `heeftAlsInitiator` | Nested BSN/vestigingsnummer | `role` (genericRole=initiator) | `rol` | Creates a Role object |
+| `heeftAlsBehandelaar` | Nested medewerker | `assignee` + role | `rol` | Handler assignment |
+
+### StUF-ZKN Status Mapping
+
+| StUF-ZKN Field | XML Path | Procest Property | Notes |
+|----------------|----------|-----------------|-------|
+| `datumStatusGezet` | `heeft/datumStatusGezet` | StatusRecord `dateSet` | Format: `YYYYMMDDHHmmss` -> ISO 8601 |
+| `gpiStatusType/code` | Nested element | StatusRecord `statusType` | Resolved via statusType lookup |
+| `statustoelichting` | `heeft/statustoelichting` | StatusRecord `description` | |
+
+### StUF-BG Person Mapping
+
+| StUF-BG Field | XML Path | Description | OpenRegister Property |
+|---------------|----------|-------------|----------------------|
+| `inp.bsn` | `npsLv01/gelijk/inp.bsn` | Burgerservicenummer | `bsn` |
+| `geslachtsnaam` | `npsLa01/antwoord/.../geslachtsnaam` | Family name | `lastName` |
+| `voorvoegselGeslachtsnaam` | Nested element | Name prefix | `namePrefix` |
+| `voornamen` | Nested element | Given names | `firstName` |
+| `geboortedatum` | `inp.geboortedatum` | Date of birth | `dateOfBirth` |
+| `verblijfsadres` | Nested AOA element | Residential address | `address` (composite) |
+| `sub.verblijfBuitenland` | Nested element | Foreign address | `foreignAddress` |
+
+### Confidentiality Enum Mapping
+
+| StUF Value | Procest Value | ZGW Dutch |
+|------------|--------------|-----------|
+| `OPENBAAR` | `public` | openbaar |
+| `BEPERKT OPENBAAR` | `restricted` | beperkt_openbaar |
+| `INTERN` | `internal` | intern |
+| `ZAAKVERTROUWELIJK` | `case_sensitive` | zaakvertrouwelijk |
+| `VERTROUWELIJK` | `confidential` | vertrouwelijk |
+| `CONFIDENTIEEL` | `highly_confidential` | confidentieel |
+| `GEHEIM` | `secret` | geheim |
+| `ZEER GEHEIM` | `top_secret` | zeer_geheim |
+
+---
+
+## ADDED Requirements
+---
+
+### Requirement: REQ-STUF-001 — Outbound StUF-BG Person Lookup via OpenConnector
+
+The system MUST support querying external StUF-BG services for person data (BRP) via OpenConnector's SOAP infrastructure. This enables case handlers to look up citizen information by BSN when creating or processing cases.
+
+**Feature tier**: V1
+
+#### Scenario: Look up person by BSN
+
+- **GIVEN** an OpenConnector source configured with type `soap` pointing to a StUF-BG endpoint (e.g., `https://brp.gemeente.nl/StUF/bg0310`)
+- **AND** the source has valid authentication (mTLS certificate or WS-Security credentials)
+- **AND** the source has stuurgegevens configured (zender: `Procest`, ontvanger: `BRP`)
+- **WHEN** a case handler requests person lookup for BSN `999993653`
+- **THEN** the system MUST construct a StUF-BG `npsLv01` SOAP envelope with the BSN in `gelijk/inp.bsn`
+- **AND** send it to the configured StUF-BG endpoint via OpenConnector's SOAPService
+- **AND** parse the `npsLa01` response extracting: `geslachtsnaam`, `voorvoegselGeslachtsnaam`, `voornamen`, `geboortedatum`, `verblijfsadres`
+- **AND** return the person data as a JSON object to the Procest frontend
+
+#### Scenario: Person not found in BRP
+
+- **GIVEN** a valid StUF-BG source configuration
+- **WHEN** a lookup is performed for BSN `000000000` (non-existent)
+- **THEN** the StUF-BG service returns a `npsLa01` response with zero results (empty `antwoord`)
+- **AND** the system MUST display: "No person found for BSN 000000000"
+
+#### Scenario: StUF-BG fault handling
+
+- **GIVEN** a valid StUF-BG source configuration
+- **WHEN** the StUF service returns a `Fo02` fault message (e.g., `StUF055: Niet geautoriseerd`)
+- **THEN** the system MUST parse the fault code and fault string from the `Fo02` envelope
+- **AND** return a structured error: `{ "code": "StUF055", "message": "Niet geautoriseerd", "detail": "..." }`
+- **AND** log the fault at WARNING level
+
+#### Scenario: Look up person by name and date of birth
+
+- **GIVEN** a valid StUF-BG source configuration
+- **AND** the case handler does not have the BSN but has the person's name and date of birth
+- **WHEN** the handler searches with `geslachtsnaam` = "Moulin" and `geboortedatum` = "19750312"
+- **THEN** the system MUST construct a `npsLv01` with name and date in `gelijk` elements
+- **AND** parse the `npsLa01` response which may contain multiple results
+- **AND** present the results as a selectable list showing BSN, full name, date of birth, and address
+
+#### Scenario: Timeout handling for BRP queries
+
+- **GIVEN** a valid StUF-BG source configuration with a 10-second timeout
+- **WHEN** the BRP endpoint does not respond within 10 seconds
+- **THEN** the system MUST abort the SOAP call and return an error: "BRP service niet beschikbaar (timeout na 10 seconden)"
+- **AND** log the timeout at WARNING level with the endpoint URL
+- **AND** the case handler MUST be able to continue case processing without the BRP data
+
+---
+
+### Requirement: REQ-STUF-002 — Outbound StUF-ZKN Case Notification
+
+The system MUST support sending case status updates to legacy systems via StUF-ZKN messages. This enables Procest to notify legacy zaaksystemen or DMS systems when case events occur.
+
+**Feature tier**: V1
+
+#### Scenario: Send status update notification
+
+- **GIVEN** an OpenConnector endpoint configured as type `soap` pointing to a legacy zaaksysteem's StUF-ZKN service
+- **AND** the case "2026-042" has its status changed from "Ontvangen" to "In behandeling"
+- **AND** the case type is configured with a StUF notification endpoint
+- **WHEN** the status change is committed
+- **THEN** the system MUST construct a StUF-ZKN `zakLk01` SOAP envelope with `mutatiesoort="W"` (wijziging)
+- **AND** include the updated zaak data: `identificatie`, `omschrijving`, `status` (with `datumStatusGezet` and status type code)
+- **AND** populate `stuurgegevens` with: `zender` (Procest organization code), `ontvanger` (legacy system code), `referentienummer` (UUID), `tijdstipBericht` (current ISO timestamp)
+- **AND** send the message via OpenConnector's SOAPService
+
+#### Scenario: Send case creation notification
+
+- **GIVEN** a StUF notification endpoint is configured
+- **WHEN** a new case "2026-043" is created via the Procest UI or ZGW API
+- **THEN** the system MUST construct a StUF-ZKN `zakLk01` SOAP envelope with `mutatiesoort="T"` (toevoeging)
+- **AND** include all initial zaak data including initiator role (`heeftAlsInitiator`)
+- **AND** send the message via OpenConnector
+
+#### Scenario: Handle notification delivery failure
+
+- **GIVEN** a StUF notification endpoint is configured but unreachable
+- **WHEN** a case status change triggers a notification
+- **AND** the SOAP call fails with a connection timeout
+- **THEN** the system MUST NOT block the status change (the case update proceeds)
+- **AND** the system MUST log the delivery failure at ERROR level
+- **AND** the system SHOULD queue the notification for retry (via OpenConnector's retry mechanism)
+- **AND** the audit trail MUST record: "StUF notification to [endpoint] failed: connection timeout"
+
+#### Scenario: Send case closure notification
+
+- **GIVEN** a case "2026-042" with status "In behandeling"
+- **AND** a result "Toegekend" is set on the case, triggering automatic status transition to "Afgerond"
+- **WHEN** the closure status is committed
+- **THEN** the system MUST send a `zakLk01` with `mutatiesoort="W"` containing the final status, result, and end date
+- **AND** if an archival action date is set, it MUST be included as `archiefactiedatum`
+
+#### Scenario: Selective notification per case type
+
+- **GIVEN** case type "Omgevingsvergunning" is configured with StUF notification to endpoint A
+- **AND** case type "Bezwaar" is configured with StUF notification to endpoint B
+- **AND** case type "Melding" has no StUF notification configured
+- **WHEN** a status change occurs on a "Melding" case
+- **THEN** NO StUF notification MUST be sent
+- **AND** the audit trail MUST NOT contain a StUF notification entry
+
+---
+
+### Requirement: REQ-STUF-003 — Outbound StUF-ZKN Document Linking
+
+The system MUST support sending document metadata to legacy DMS systems via StUF-ZKN `edcLk01` messages when documents are uploaded to a case. OpenConnector's SOAPService already has `edcLk01` awareness (base64 content handling).
+
+**Feature tier**: V1
+
+#### Scenario: Notify legacy DMS of new document
+
+- **GIVEN** a case "2026-042" linked to a StUF-ZKN endpoint
+- **AND** a document "Bouwtekening.pdf" (1.2 MB) is uploaded to the case
+- **WHEN** the document upload is committed
+- **THEN** the system MUST construct a StUF-ZKN `edcLk01` SOAP envelope with `mutatiesoort="T"`
+- **AND** include: `identificatie` (document ID), `titel` ("Bouwtekening.pdf"), `formaat` ("application/pdf"), `inhoud` (base64-encoded content), `vertrouwelijkAanduiding`
+- **AND** include the zaak reference: `isRelevantVoor/gerelateerde/identificatie` = "2026-042"
+- **AND** send via OpenConnector's SOAPService (which already handles `edcLk01` content decoding)
+
+#### Scenario: Large document handling
+
+- **GIVEN** a document larger than 20 MB
+- **WHEN** the system prepares the `edcLk01` message
+- **THEN** the system SHOULD use MTOM (Message Transmission Optimization Mechanism) for binary content
+- OR the system MAY skip the `inhoud` element and include only metadata with a download reference
+
+#### Scenario: Document version update notification
+
+- **GIVEN** a document "Bouwtekening.pdf" previously sent via `edcLk01` with `mutatiesoort="T"`
+- **AND** the document is replaced with a new version "Bouwtekening_v2.pdf"
+- **WHEN** the document update is committed
+- **THEN** the system MUST send a `edcLk01` with `mutatiesoort="W"` (wijziging)
+- **AND** include the new document content and the same `identificatie` as the original
+- **AND** include `versie` = "2" to indicate the version number
+
+---
+
+### Requirement: REQ-STUF-004 — StUF Stuurgegevens Configuration
+
+The system MUST support configuring StUF `stuurgegevens` (message routing metadata) for each StUF endpoint. Stuurgegevens are mandatory on every StUF message and identify the sender and receiver.
+
+**Feature tier**: V1
+
+#### Scenario: Configure stuurgegevens for a StUF source
+
+- **GIVEN** an admin configuring a new StUF endpoint in OpenConnector
+- **WHEN** the admin opens the source configuration
+- **THEN** the form MUST include fields for:
+ - `zender.organisatie` (sending organization code, e.g., "0363" for Amsterdam)
+ - `zender.applicatie` (sending application name, e.g., "Procest")
+ - `ontvanger.organisatie` (receiving organization code)
+ - `ontvanger.applicatie` (receiving application name)
+- **AND** these values MUST be stored with the source configuration
+
+#### Scenario: Stuurgegevens populated on outbound messages
+
+- **GIVEN** a StUF source with stuurgegevens configured: zender = `{ organisatie: "0363", applicatie: "Procest" }`, ontvanger = `{ organisatie: "0363", applicatie: "BRP" }`
+- **WHEN** any outbound StUF message is constructed
+- **THEN** the `stuurgegevens` element MUST contain:
+ - `zender/organisatie` = "0363"
+ - `zender/applicatie` = "Procest"
+ - `ontvanger/organisatie` = "0363"
+ - `ontvanger/applicatie` = "BRP"
+ - `referentienummer` = newly generated UUID
+ - `tijdstipBericht` = current timestamp in `YYYYMMDDHHmmss` format
+
+#### Scenario: Cross-reference in response messages
+
+- **GIVEN** a StUF source sent a message with `referentienummer` = "550e8400-e29b-41d4-a716-446655440000"
+- **WHEN** the external service returns a response (La01, Bv01, or Fo01)
+- **THEN** the response `stuurgegevens` MUST contain `crossRefnummer` matching the original `referentienummer`
+- **AND** the system MUST validate that `crossRefnummer` matches a known outbound `referentienummer`
+- **AND** if no match is found, the response MUST be logged at WARNING level and discarded
+
+---
+
+### Requirement: REQ-STUF-005 — StUF-ZKN zaakIdentificatie Generation
+
+The system SHALL support the `genereerZaakIdentificatie` service call for obtaining zaak identifiers from external systems that manage identifier sequences.
+
+**Feature tier**: V1
+
+#### Scenario: Obtain identifier from legacy system
+
+- **GIVEN** a StUF-ZKN endpoint that supports `genereerZaakIdentificatie_Di02`
+- **AND** the case type "Omgevingsvergunning" is configured to use external identifier generation
+- **WHEN** a new case of this type is being created
+- **THEN** the system MUST send a `genereerZaakIdentificatie_Di02` message to the configured endpoint
+- **AND** parse the `genereerZaakIdentificatie_Du02` response to extract the `zaakidentificatie`
+- **AND** use this identifier as the case's `identifier` instead of auto-generating one
+
+#### Scenario: Fallback to local generation
+
+- **GIVEN** a case type configured for external identifier generation
+- **AND** the external StUF endpoint is unreachable
+- **WHEN** a new case is being created
+- **THEN** the system MUST fall back to local identifier generation (format: `YYYY-NNN`)
+- **AND** log a warning: "External zaakidentificatie generation failed, using local identifier"
+
+#### Scenario: Identifier uniqueness validation
+
+- **GIVEN** a StUF-ZKN endpoint returns identifier "2026-042"
+- **AND** a case with identifier "2026-042" already exists in OpenRegister
+- **WHEN** the system processes the `Du02` response
+- **THEN** the system MUST reject the duplicate identifier
+- **AND** request a new identifier by sending another `Di02` message
+- **AND** if 3 consecutive duplicates are returned, fall back to local generation with a UUID-based identifier and log an ERROR
+
+---
+
+### Requirement: REQ-STUF-006 — Outbound StUF Authentication
+
+The system MUST support the authentication methods required by Dutch government StUF endpoints. OpenConnector's existing certificate handling and AuthenticationService provide the foundation.
+
+**Feature tier**: V1
+
+#### Scenario: mTLS with PKIoverheid certificate
+
+- **GIVEN** a StUF endpoint requiring PKIoverheid mutual TLS
+- **AND** the admin has uploaded a client certificate and private key in OpenConnector's source configuration
+- **WHEN** the system sends a StUF SOAP message
+- **THEN** the SOAP call MUST include the client certificate for mTLS
+- **AND** the server's certificate MUST be validated against the PKIoverheid certificate chain
+
+#### Scenario: WS-Security UsernameToken
+
+- **GIVEN** a StUF endpoint requiring WS-Security UsernameToken authentication
+- **AND** the admin has configured username and password in the source configuration
+- **WHEN** the system sends a StUF SOAP message
+- **THEN** the SOAP envelope Header MUST include a `wsse:Security` element with a `wsse:UsernameToken` containing the configured credentials
+- **AND** the password SHOULD be sent as `wsse:Password Type="PasswordDigest"` (nonce + timestamp + password hash)
+
+#### Scenario: Certificate renewal warning
+
+- **GIVEN** a StUF source with a PKIoverheid client certificate expiring in 30 days
+- **WHEN** an admin views the source configuration
+- **THEN** the system SHOULD display a warning: "Client certificate expires on [date]. Renew before expiry to prevent service interruption."
+- **AND** at 7 days before expiry, a Nextcloud notification SHOULD be sent to all admin users
+
+#### Scenario: Certificate chain validation failure
+
+- **GIVEN** a StUF endpoint with a server certificate signed by a CA not in the PKIoverheid chain
+- **WHEN** the system attempts to send a SOAP message
+- **THEN** the SOAP call MUST fail with an error: "Server certificate validation failed: unknown CA"
+- **AND** the system MUST NOT send the SOAP message content to an untrusted endpoint
+- **AND** the error MUST be logged at ERROR level with the certificate details
+
+---
+
+### Requirement: REQ-STUF-007 — Inbound StUF-ZKN Case Creation
+
+The system MUST accept incoming StUF-ZKN `zakLk01` messages (with `mutatiesoort="T"`) to create cases from legacy form systems or legacy case systems pushing data to Procest. This is the SOAP server challenge -- Nextcloud routes are REST-based, so the inbound StUF endpoint is implemented as a raw POST handler that parses SOAP XML.
+
+**Feature tier**: V2
+
+#### Scenario: Receive zakLk01 to create a case
+
+- **GIVEN** a Procest StUF endpoint exposed at `/apps/procest/api/stuf/zaken`
+- **AND** the endpoint accepts `POST` with `Content-Type: text/xml` or `application/soap+xml`
+- **WHEN** a legacy formulierensysteem sends a StUF-ZKN `zakLk01` SOAP message with `mutatiesoort="T"` containing:
+ - `omschrijving` = "Aanvraag omgevingsvergunning Dorpsstraat 1"
+ - `startdatum` = "20260316"
+ - `zaaktype/code` = "OV-001"
+ - `heeftAlsInitiator/gerelateerde/inp.bsn` = "999993653"
+- **THEN** the system MUST parse the SOAP envelope and extract the StUF-ZKN message body
+- **AND** map `omschrijving` to case `title`
+- **AND** convert `startdatum` from `YYYYMMDD` to ISO 8601 date
+- **AND** resolve `zaaktype/code` "OV-001" to the matching Procest case type
+- **AND** create an OpenRegister object in the `procest` register with the `case` schema
+- **AND** create a Role object with `genericRole = "initiator"` and BSN `999993653`
+- **AND** auto-calculate the `deadline` from the case type's `processingDeadline`
+- **AND** return a StUF `Bv01` (bevestigingsbericht) with the generated `zaakidentificatie`
+
+#### Scenario: Reject zakLk01 with unknown case type
+
+- **GIVEN** the StUF endpoint receives a `zakLk01` with `zaaktype/code` = "UNKNOWN-001"
+- **AND** no Procest case type has a matching code
+- **WHEN** the message is processed
+- **THEN** the system MUST return a StUF `Fo01` fault message with:
+ - `foutcode` = "StUF058"
+ - `foutbeschrijving` = "Onbekend zaaktype: UNKNOWN-001"
+ - `plek` = "server"
+
+#### Scenario: Reject invalid XML
+
+- **GIVEN** the StUF endpoint receives a POST with malformed XML
+- **WHEN** the system attempts to parse the message
+- **THEN** the system MUST return a SOAP Fault with `faultcode = "Client"` and `faultstring = "Ongeldig XML bericht"`
+
+#### Scenario: Validate stuurgegevens on inbound messages
+
+- **GIVEN** the StUF endpoint is configured with expected `ontvanger` codes
+- **WHEN** a `zakLk01` arrives with `ontvanger/applicatie` = "WrongApp"
+- **THEN** the system MUST return a StUF `Fo01` fault with `foutcode` = "StUF001" and `foutbeschrijving` = "Onbekende ontvanger"
+
+#### Scenario: Case creation with multiple roles
+
+- **GIVEN** a `zakLk01` with `mutatiesoort="T"` containing:
+ - `heeftAlsInitiator/gerelateerde/inp.bsn` = "999993653"
+ - `heeftAlsGemachtigde/gerelateerde/vestigingsNummer` = "000012345678"
+ - `heeftAlsBelanghebbende/gerelateerde/inp.bsn` = "999990627"
+- **WHEN** the message is processed
+- **THEN** the system MUST create three Role objects: initiator (BSN), gemachtigde (vestigingsnummer), belanghebbende (BSN)
+- **AND** all three roles MUST be linked to the newly created case
+
+---
+
+### Requirement: REQ-STUF-008 — Inbound StUF-ZKN Case Query
+
+The system MUST accept incoming StUF-ZKN `zakLv01` (zaak opvragen) messages and respond with `zakLa01` (zaak antwoord) messages containing case data from OpenRegister.
+
+**Feature tier**: V2
+
+#### Scenario: Query case by identifier
+
+- **GIVEN** a case "2026-042" exists in the `procest` register with title "Bouwvergunning Keizersgracht 100", status "In behandeling", startDate "2026-01-15"
+- **WHEN** a legacy system sends a `zakLv01` with `gelijk/identificatie` = "2026-042"
+- **THEN** the system MUST return a `zakLa01` SOAP response containing:
+ - `identificatie` = "2026-042"
+ - `omschrijving` = "Bouwvergunning Keizersgracht 100"
+ - `startdatum` = "20260115"
+ - Current status with `datumStatusGezet` and status type code
+ - Related roles (`heeftAlsInitiator`, `heeftAlsBehandelaar`)
+ - Related documents (if `scope` requests them)
+
+#### Scenario: Query with scope filtering
+
+- **GIVEN** a `zakLv01` request with a `scope` element requesting only `identificatie`, `omschrijving`, and `startdatum`
+- **WHEN** the system processes the request
+- **THEN** the `zakLa01` response MUST include only the requested fields
+- **AND** omitted fields MUST NOT appear in the response (not even as empty elements)
+
+#### Scenario: Query with no results
+
+- **GIVEN** a `zakLv01` with `gelijk/identificatie` = "9999-999" (non-existent)
+- **WHEN** the system processes the request
+- **THEN** the system MUST return a `zakLa01` with an empty `antwoord` element (zero objects)
+
+#### Scenario: Query with maximumAantal
+
+- **GIVEN** 50 cases matching the query criteria
+- **AND** the `zakLv01` specifies `maximumAantal` = 10
+- **WHEN** the system processes the request
+- **THEN** the `zakLa01` MUST contain at most 10 zaak objects
+
+#### Scenario: Query cases by date range
+
+- **GIVEN** 20 cases with `startdatum` between "20260101" and "20260331"
+- **WHEN** a `zakLv01` queries with `van/startdatum` = "20260201" and `totEnMet/startdatum` = "20260228"
+- **THEN** the `zakLa01` MUST contain only cases with `startdatum` in February 2026
+- **AND** the response MUST include `stuurgegevens` with `crossRefnummer` matching the query's `referentienummer`
+
+---
+
+### Requirement: REQ-STUF-009 — Inbound StUF-ZKN Case Update
+
+The system MUST accept incoming StUF-ZKN `zakLk01` messages with `mutatiesoort="W"` (wijziging) to update existing cases.
+
+**Feature tier**: V2
+
+#### Scenario: Update case via zakLk01
+
+- **GIVEN** a case "2026-042" exists with title "Bouwvergunning Keizersgracht 100"
+- **WHEN** a legacy system sends a `zakLk01` with `mutatiesoort="W"` and `identificatie` = "2026-042" and `omschrijving` = "Bouwvergunning Keizersgracht 100 - gewijzigd"
+- **THEN** the system MUST update the case title to "Bouwvergunning Keizersgracht 100 - gewijzigd"
+- **AND** the audit trail MUST record the update with source "StUF-ZKN"
+- **AND** the system MUST return a `Bv01` bevestiging
+
+#### Scenario: Update case status via zakLk01
+
+- **GIVEN** a case "2026-042" at status "Ontvangen"
+- **WHEN** a `zakLk01` with `mutatiesoort="W"` includes a new status element with `gpiStatusType/code` = "INBEH" and `datumStatusGezet` = "20260316120000"
+- **THEN** the system MUST resolve "INBEH" to the matching Procest status type
+- **AND** create a new StatusRecord with the specified date
+- **AND** all case type validation rules MUST be enforced (required properties, required documents)
+
+#### Scenario: Reject update for non-existent case
+
+- **GIVEN** no case with identifier "9999-999" exists
+- **WHEN** a `zakLk01` with `mutatiesoort="W"` and `identificatie` = "9999-999" arrives
+- **THEN** the system MUST return a `Fo01` fault with `foutcode` = "StUF064" and `foutbeschrijving` = "Zaak niet gevonden: 9999-999"
+
+#### Scenario: Partial update with only changed fields
+
+- **GIVEN** a case "2026-042" with title, description, startDate, and deadline set
+- **WHEN** a `zakLk01` with `mutatiesoort="W"` includes only `toelichting` = "Aangepaste toelichting"
+- **THEN** the system MUST update only the `description` field
+- **AND** all other fields (title, startDate, deadline) MUST remain unchanged
+- **AND** the `Bv01` response MUST confirm the update
+
+#### Scenario: Reject update on closed case
+
+- **GIVEN** a case "2026-042" with status "Afgerond" (closed)
+- **WHEN** a `zakLk01` with `mutatiesoort="W"` attempts to change the `omschrijving`
+- **THEN** the system MUST enforce the `ZgwZrcRulesService` immutability rules
+- **AND** return a `Fo01` fault with `foutcode` = "StUF062" and `foutbeschrijving` = "Zaak is afgesloten en kan niet meer worden gewijzigd"
+
+---
+
+### Requirement: REQ-STUF-010 — Inbound StUF-ZKN Document Handling
+
+The system MUST accept incoming StUF-ZKN `edcLk01` messages to link documents to cases.
+
+**Feature tier**: V2
+
+#### Scenario: Receive document via edcLk01
+
+- **GIVEN** a case "2026-042" exists
+- **WHEN** a legacy system sends an `edcLk01` with `mutatiesoort="T"` containing:
+ - `identificatie` = "DOC-2026-001"
+ - `titel` = "Bouwtekening.pdf"
+ - `formaat` = "application/pdf"
+ - `inhoud` = base64-encoded PDF content
+ - `isRelevantVoor/gerelateerde/identificatie` = "2026-042"
+- **THEN** the system MUST decode the base64 `inhoud`
+- **AND** store the document in Nextcloud Files under the case's folder
+- **AND** create a caseDocument object linking the document to the case
+- **AND** return a `Bv01` bevestiging
+
+#### Scenario: Document without content (metadata only)
+
+- **GIVEN** an `edcLk01` with document metadata but no `inhoud` element
+- **WHEN** the message is processed
+- **THEN** the system MUST create a caseDocument object with the metadata
+- **AND** mark the document as "metadata only -- no content received"
+
+#### Scenario: Document linked to non-existent case
+
+- **GIVEN** an `edcLk01` with `isRelevantVoor/gerelateerde/identificatie` = "9999-999"
+- **AND** no case with that identifier exists
+- **WHEN** the message is processed
+- **THEN** the system MUST return a `Fo01` fault with `foutcode` = "StUF064" and `foutbeschrijving` = "Zaak niet gevonden: 9999-999"
+
+---
+
+### Requirement: REQ-STUF-011 — StUF XML Message Processing
+
+The system MUST correctly handle StUF XML namespaces, date formats, noValue attributes, and message structure.
+
+**Feature tier**: V1 (outbound), V2 (inbound)
+
+#### Scenario: XML namespace handling
+
+- **GIVEN** a StUF-ZKN message is being constructed or parsed
+- **THEN** the system MUST correctly handle these namespaces:
+ - `http://www.egem.nl/StUF/StUF0301` (StUF base)
+ - `http://www.egem.nl/StUF/sector/zkn/0310` (StUF-ZKN)
+ - `http://www.egem.nl/StUF/sector/bg/0310` (StUF-BG)
+ - `http://www.w3.org/2001/XMLSchema-instance` (xsi)
+ - `http://www.opengis.net/gml` (gml, for geometry)
+
+#### Scenario: Date format conversion
+
+- **GIVEN** a date value "2026-03-16" in ISO 8601 format (Procest internal)
+- **WHEN** the value is included in an outbound StUF message
+- **THEN** the value MUST be converted to StUF format: "20260316"
+- **AND** datetime values MUST use format: "YYYYMMDDHHmmss" (e.g., "20260316143000")
+
+#### Scenario: noValue attribute handling
+
+- **GIVEN** a case property that has no value (null/empty)
+- **WHEN** the property is included in an outbound StUF message
+- **THEN** the element MUST include the appropriate `StUF:noValue` attribute:
+ - `geenWaarde` -- explicitly set to no value
+ - `waardeOnbekend` -- value exists but is unknown
+ - `nietOndersteund` -- field not supported by the system
+ - `vastgesteldOnbekend` -- officially determined as unknown
+
+#### Scenario: Validate outbound XML against XSD
+
+- **GIVEN** bundled StUF-ZKN 3.10 and StUF-BG 3.10 XSD schemas
+- **WHEN** an outbound StUF message is constructed
+- **THEN** the system SHOULD validate the XML against the relevant XSD before sending
+- **AND** if validation fails, the system MUST log the validation errors and NOT send the invalid message
+
+#### Scenario: Handle XML entities and special characters
+
+- **GIVEN** a case title containing special characters: `Vergunning "Café & Bar" `
+- **WHEN** the title is included in an outbound StUF message
+- **THEN** XML entities MUST be properly escaped: `&`, `<`, `>`, `"`
+- **AND** the resulting XML MUST be well-formed and parseable
+
+---
+
+### Requirement: REQ-STUF-012 — StUF-BG Inbound Person Query
+
+The system SHALL accept incoming StUF-BG `npsLv01` messages to expose person data stored in OpenRegister. This enables legacy systems to query Procest as if it were a BRP source.
+
+**Feature tier**: V2
+
+#### Scenario: Receive person query
+
+- **GIVEN** the Procest StUF endpoint at `/apps/procest/api/stuf/personen`
+- **AND** a person object with BSN "999993653" exists in OpenRegister
+- **WHEN** a legacy system sends a StUF-BG `npsLv01` with `gelijk/inp.bsn` = "999993653"
+- **THEN** the system MUST return a `npsLa01` response with the person data mapped from OpenRegister to StUF-BG XML
+
+#### Scenario: Person query with scope
+
+- **GIVEN** a `npsLv01` with scope requesting only `inp.bsn`, `geslachtsnaam`, and `geboortedatum`
+- **WHEN** the system processes the request
+- **THEN** the `npsLa01` MUST include only the requested fields
+
+#### Scenario: Person query with wildcard search
+
+- **GIVEN** 5 persons in OpenRegister with `geslachtsnaam` starting with "Jan"
+- **WHEN** a `npsLv01` queries with `gelijk/geslachtsnaam` = "Jan*" (wildcard)
+- **THEN** the system MUST return all 5 matching persons in the `npsLa01` response
+- **AND** results MUST be ordered by `geslachtsnaam` ascending
+
+---
+
+### Requirement: REQ-STUF-013 — StUF Field Mapping Configuration
+
+The system MUST store field mappings between StUF XML paths and OpenRegister object properties as configurable mapping objects. Default mappings for ZGW-zaak and BRP-person data MUST be pre-seeded.
+
+**Feature tier**: V1
+
+#### Scenario: Pre-seeded zaak field mapping
+
+- **GIVEN** the Procest app is installed and the repair step runs
+- **THEN** a default StUF-ZKN field mapping MUST be created in OpenRegister containing mappings for all fields listed in the "StUF-ZKN to Procest Case Mapping" table above
+- **AND** the mapping MUST include date format transformations (StUF `YYYYMMDD` to ISO 8601 and vice versa)
+
+#### Scenario: Custom field mapping
+
+- **GIVEN** a municipality uses a StUF extension with custom fields (e.g., `gem:kenmerk` for a local reference number)
+- **WHEN** an admin adds a custom mapping entry: StUF path `gem:kenmerk` -> OpenRegister property `localReference`
+- **THEN** the system MUST apply this mapping during StUF message parsing and construction
+- **AND** the custom mapping MUST NOT override default mappings unless explicitly configured
+
+#### Scenario: Value transformation in mapping
+
+- **GIVEN** a mapping entry for `vertrouwelijkAanduiding` with a value transformation table (StUF enum values to Procest enum values)
+- **WHEN** a StUF message contains `vertrouwelijkAanduiding` = "ZAAKVERTROUWELIJK"
+- **THEN** the system MUST transform the value to `case_sensitive` using the mapping's transformation table
+
+#### Scenario: Mapping object schema structure
+
+- **GIVEN** the StUF field mapping is stored as an OpenRegister object
+- **THEN** the mapping schema MUST include:
+ - `name` (string): mapping set name (e.g., "StUF-ZKN to Procest Case")
+ - `sourceFormat` (enum): "stuf-zkn" | "stuf-bg"
+ - `targetSchema` (string): OpenRegister schema reference (e.g., "case", "person")
+ - `fieldMappings` (array): each entry with `{ stufPath, openRegisterProperty, dataType, transformation, required }`
+ - `dateFormat` (string): date format pattern for this mapping set
+ - `isDefault` (boolean): whether this is a system-provided mapping
+
+#### Scenario: Export and import mapping configurations
+
+- **GIVEN** an admin who configured custom StUF mappings for municipality A
+- **WHEN** the admin exports the mapping configuration
+- **THEN** the system MUST produce a JSON file containing all custom mapping entries
+- **AND** the exported file MUST be importable on another Procest instance for municipality B
+
+---
+
+### Requirement: REQ-STUF-014 — SOAP Server Within Nextcloud
+
+The system SHALL provide a SOAP server within Nextcloud for exposing inbound StUF endpoints. Since Nextcloud routes are REST-based, the StUF controller accepts raw XML POSTs and processes them as SOAP messages without using PHP's built-in SoapServer (which requires WSDL mode and conflicts with Nextcloud's routing).
+
+**Feature tier**: V2
+
+#### Scenario: Raw SOAP POST handling
+
+- **GIVEN** a Procest route `/apps/procest/api/stuf/{service}` registered as a raw POST endpoint
+- **WHEN** a SOAP message arrives with `Content-Type: text/xml; charset=utf-8`
+- **THEN** the controller MUST read the raw request body (`php://input`)
+- **AND** parse the SOAP envelope using PHP's DOMDocument or SimpleXML
+- **AND** extract the SOAP Body content
+- **AND** dispatch to the appropriate handler based on the root element name (e.g., `zakLk01`, `zakLv01`, `npsLv01`)
+- **AND** construct a SOAP envelope response with the appropriate content
+- **AND** return with `Content-Type: text/xml; charset=utf-8`
+
+#### Scenario: WSDL serving
+
+- **GIVEN** bundled WSDL files for StUF-ZKN and StUF-BG services
+- **WHEN** a client sends a GET request to `/apps/procest/api/stuf/zaken?wsdl`
+- **THEN** the system MUST return the StUF-ZKN WSDL file with `Content-Type: text/xml`
+- **AND** the WSDL's `soap:address location` MUST reflect the actual Procest endpoint URL
+
+#### Scenario: SOAPAction header routing
+
+- **GIVEN** a SOAP request with `SOAPAction: "http://www.egem.nl/StUF/sector/zkn/0310/zakLk01"`
+- **WHEN** the controller processes the request
+- **THEN** the SOAPAction header MAY be used as a secondary dispatch mechanism alongside XML body inspection
+
+#### Scenario: Request logging for audit compliance
+
+- **GIVEN** the Procest StUF endpoint receives a SOAP message
+- **THEN** the system MUST log: timestamp, source IP, SOAPAction header, message type (zakLk01/zakLv01/etc.), stuurgegevens (zender/ontvanger), processing result (success/fault)
+- **AND** the raw XML MUST be stored in a separate audit log (configurable retention period)
+- **AND** sensitive data (BSN, personal names) MUST be masked in standard log output but preserved in the audit log
+
+---
+
+### Requirement: REQ-STUF-015 — Dual API Coexistence
+
+The system MUST ensure that StUF and ZGW APIs provide consistent views of the same data. Changes made via one protocol MUST be immediately visible via the other.
+
+**Feature tier**: V1
+
+#### Scenario: Case created via StUF visible in ZGW
+
+- **GIVEN** a case created via inbound StUF-ZKN `zakLk01` with identifier "2026-042"
+- **WHEN** a ZGW API client sends GET `/api/v1/zaken?identificatie=2026-042` to the ZrcController
+- **THEN** the case MUST be returned with all fields populated from the StUF-created data
+- **AND** the `url` field MUST contain the ZGW-style self-link
+
+#### Scenario: Case created via ZGW queryable via StUF
+
+- **GIVEN** a case created via the ZGW Zaken API with identifier "2026-043"
+- **WHEN** a legacy system sends a StUF-ZKN `zakLv01` with `gelijk/identificatie` = "2026-043"
+- **THEN** the case MUST be returned in the `zakLa01` response with all fields correctly mapped to StUF-ZKN XML
+
+#### Scenario: Case updated via UI reflected in StUF query
+
+- **GIVEN** a case "2026-042" with status "Ontvangen"
+- **WHEN** a handler changes the status to "In behandeling" via the Procest frontend
+- **AND** a legacy system immediately sends a `zakLv01` for "2026-042"
+- **THEN** the response MUST show the new status "In behandeling" with the correct `datumStatusGezet`
+
+#### Scenario: StUF and ZGW audit trail unification
+
+- **GIVEN** a case "2026-042" that was:
+ 1. Created via StUF-ZKN `zakLk01`
+ 2. Updated via ZGW Zaken API
+ 3. Queried via StUF-ZKN `zakLv01`
+- **WHEN** viewing the case audit trail in the Procest frontend
+- **THEN** all three actions MUST appear in chronological order
+- **AND** each entry MUST show the protocol used ("StUF-ZKN", "ZGW API", "Frontend")
+- **AND** the audit trail MUST be queryable by protocol type
+
+---
+
+## SOAP Server Challenge
+
+Hosting a SOAP server within Nextcloud presents architectural challenges:
+
+1. **Nextcloud routing is REST-based**: All routes go through `routes.php` and expect JSON request/response. StUF requires raw XML POST handling with SOAP envelope wrapping.
+
+2. **PHP SoapServer limitations**: PHP's built-in `SoapServer` class operates in WSDL mode and expects to handle the full HTTP lifecycle. Within Nextcloud's controller framework, this conflicts with the existing request/response handling.
+
+3. **Proposed solution**: Implement a `StufController` that extends `OCP\AppFramework\Controller` and:
+ - Registers routes for `/api/stuf/zaken` and `/api/stuf/personen`
+ - Reads raw XML from `php://input`
+ - Parses XML using DOMDocument (not SoapServer)
+ - Dispatches to a `StufMessageHandler` service
+ - Constructs SOAP XML responses manually
+ - Returns `DataDisplayResponse` with `Content-Type: text/xml`
+
+4. **Alternative approach**: Use OpenConnector as a SOAP proxy -- OpenConnector could host the SOAP endpoint (it already has SOAPService) and forward parsed data to Procest's REST API. This avoids the SOAP-in-Nextcloud problem but adds a network hop and dependency.
+
+---
+
+## Dependencies
+
+- **OpenConnector stuf-adapter spec** (`openconnector/openspec/specs/stuf-adapter/spec.md`): Provides the SOAP infrastructure (SOAPService), certificate handling, and source type configuration. Procest leverages OpenConnector for all outbound StUF communication.
+- **OpenRegister**: All case data stored as objects; field mapping configurations stored as OpenRegister objects.
+- **Procest case-management spec** (`../case-management/spec.md`): The case data model, status lifecycle, validation rules, and audit trail that StUF messages map to/from.
+- **Procest roles-decisions spec** (`../roles-decisions/spec.md`): Role entities created from StUF `heeftAlsInitiator`/`heeftAlsBehandelaar` data.
+- **Procest openregister-integration spec** (`../openregister-integration/spec.md`): The `procest` register and 12 schemas that store all data.
+- **PHP DOMDocument / SimpleXML**: For XML parsing and construction (bundled with PHP).
+- **StUF XSD schema packages**: StUF-BG 3.10 and StUF-ZKN 3.10 XSD files for validation.
+- **Existing ZGW controllers** (`ZrcController`, `ZtcController`, `DrcController`, `BrcController`): The REST API surface that coexists with StUF.
+
+---
+
+## Current Implementation Status
+
+### Using Mock Register Data
+
+This spec depends on the **BRP** mock register for testing StUF-BG person lookup (REQ-STUF-001) and the **BAG** mock register for address data.
+
+**Loading the registers:**
+```bash
+# Load BRP register (35 persons, register slug: "brp", schema: "ingeschreven-persoon")
+docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json
+
+# Load BAG register (32 addresses + 21 objects + 21 buildings, register slug: "bag")
+docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/bag_register.json
+```
+
+**Test data for this spec's use cases:**
+- **StUF-BG npsLv01 person lookup**: BSN `999993653` (Suzanne Moulin) -- test outbound BRP query and npsLa01 response mapping
+- **StUF-BG person not found**: BSN `000000000` -- test empty npsLa01 response handling
+- **StUF-ZKN with initiator BSN**: BSN `999990627` (Stephan Janssen) -- test inbound zakLk01 with `heeftAlsInitiator/inp.bsn`
+- **StUF-BG inbound query (REQ-STUF-012)**: BSN `999992570` (Albert Vogel) -- test serving person data via StUF-BG endpoint
+
+### Procest
+
+**No StUF implementation exists.** Grep for "stuf", "StUF", and "soap" in `procest/lib/` returns zero results. All current API communication is via ZGW REST APIs through the four ZGW controllers.
+
+**Implemented (ZGW foundation that StUF maps to):**
+- ZGW Zaken API via `ZrcController` -- zaken, statussen, resultaten, rollen, zaakeigenschappen, zaakinformatieobjecten, zaakobjecten, klantcontacten
+- ZGW Catalogi API via `ZtcController` -- zaaktypen, statustypen, resultaattypen, roltypen, besluittypen
+- ZGW Documenten API via `DrcController` -- enkelvoudiginformatieobjecten
+- ZGW Besluiten API via `BrcController` -- besluiten
+- ZGW business rules via `ZgwBusinessRulesService` and `ZgwZrcRulesService`
+- OpenRegister schemas for all 12 entity types
+
+### OpenConnector
+
+**Partial SOAP infrastructure exists** (see `openconnector/openspec/specs/stuf-adapter/spec.md` for details):
+- `SOAPService` with generic SOAP client, WSDL-driven requests, SOAP 1.1/1.2 support
+- Specific StUF-ZKN `edcLk01` handling (base64 document content decoding)
+- Source type `soap` with WSDL URL and authentication configuration
+- Certificate handling for mTLS (PKIoverheid)
+- `CallService` SOAP routing (type `soap` -> SOAPService)
+- **NOT implemented in OpenConnector**: Inbound SOAP server, StUF field mappings, WSDL bundling, stuurgegevens, namespace handling, noValue attributes, fault message handling (Fo01/Fo02/Fo03/Bv03)
+
+---
+
+## Standards & References
+
+- **StUF 3.01**: Standaard Uitwisseling Formaat, base standard. Defines the SOAP message structure, stuurgegevens, kennisgevingen (Lk01), vraag/antwoord (Lv01/La01), bevestiging (Bv01/Bv03), and foutmeldingen (Fo01/Fo02/Fo03). Maintained by VNG Realisatie. https://www.gemmaonline.nl/index.php/StUF_Berichtenstandaard
+- **StUF-ZKN 3.10**: Sectormodel Zaak-/Documentservices, built on StUF 3.01. Defines zaak (zak), document (edc), status, and related message types. The "e" extension (3.10e) adds extra message types. https://www.gemmaonline.nl/index.php/Sectormodel_Zaken:_StUF-ZKN
+- **StUF-BG 3.10**: Sectormodel Basisgegevens, built on StUF 3.01. Defines person (nps), address (adr), and other base registry data types. https://www.gemmaonline.nl/index.php/Sectormodel_Basisgegevens:_StUF-BG
+- **RGBZ 2.0 (Referentiemodel Gemeentelijke Basisgegevens Zaken)**: The information model underlying StUF-ZKN, defining zaak, status, document, besluit, and their relationships. The same model underlies ZGW APIs.
+- **ZGW APIs (VNG)**: The modern REST-based successor to StUF-ZKN. Procest already implements these via ZrcController, ZtcController, DrcController, BrcController. https://vng-realisatie.github.io/gemma-zaken/
+- **GEMMA**: Gemeentelijke Model Architectuur, the reference architecture for Dutch municipalities. Defines how StUF and ZGW fit in the municipal information landscape. https://www.gemmaonline.nl/
+- **Logius**: Dutch government IT authority responsible for PKIoverheid certificates and DigiKoppeling (the transport standard for government-to-government communication, which mandates how StUF messages are exchanged).
+- **DigiKoppeling**: Transport standard (WUS/ebMS) for Dutch government interoperability. StUF messages are typically exchanged over DigiKoppeling WUS (SOAP+WS-Security). https://www.logius.nl/domeinen/gegevensuitwisseling/digikoppeling
+- **WS-Security (OASIS)**: SOAP message security standard. UsernameToken and X.509 Token profiles are used by Dutch government StUF endpoints.
+- **PKIoverheid**: Dutch government PKI for mTLS authentication on production StUF endpoints.
+- **MTOM (Message Transmission Optimization Mechanism)**: W3C standard for efficient binary content in SOAP messages, relevant for large document transfers via edcLk01.
+
+---
+
+## Specificity Assessment
+
+### Sufficient for implementation
+- Complete field mapping tables between StUF-ZKN/BG and Procest case model
+- Clear separation of outbound (V1, via OpenConnector) and inbound (V2, SOAP server) concerns
+- Detailed Gherkin scenarios for each message type with concrete data examples
+- Explicit SOAP server architecture proposal addressing the Nextcloud routing challenge
+- Coexistence principle ensuring data consistency between StUF and ZGW APIs
+- Authentication methods (mTLS, WS-Security) aligned with existing OpenConnector capabilities
+- Reference to OpenConnector's existing SOAPService and edcLk01 handling
+
+### Missing or ambiguous
+- **StUF version negotiation**: The spec targets 3.01/3.10 but some municipalities may run older versions. Version detection and fallback behavior is not defined.
+- **Async response patterns**: StUF supports asynchronous Bv03/Fo03 callback patterns for long-running operations. The callback mechanism (how Procest receives async responses) is not detailed.
+- **Performance requirements**: No throughput or latency SLAs for SOAP message processing.
+- **Mapping object schema**: REQ-STUF-013 now defines the mapping schema structure in scenario STUF-013d.
+- **Multi-source routing**: Can multiple StUF endpoints be configured for different case types, or is there one global StUF endpoint?
+- **StUF-ZKN 3.10e extensions**: The "e" extension adds extra message types not covered in this spec.
+- **Archival-related StUF messages**: StUF defines messages for archival transfers (overbrenging) which relate to the case result archival rules but are not covered here.
+- **Rate limiting and access control**: How are inbound StUF endpoints secured beyond stuurgegevens validation? IP whitelisting? Client certificate validation on the inbound side?
+
+### Open questions
+1. Should inbound StUF endpoints be hosted in Procest directly or proxied via OpenConnector (which already has SOAPService)?
+2. Which StUF version(s) must be supported on day one -- 3.01 only, 3.10 only, or both?
+3. Should the field mapping configuration be stored in the `procest` register or in OpenConnector's register?
+4. How should large document content in inbound `edcLk01` messages be handled -- streamed to disk or loaded into memory?
+5. Is DigiKoppeling (WUS profile) compliance required, or is plain HTTPS with WS-Security sufficient?
diff --git a/openspec/changes/stuf-support/tasks.md b/openspec/changes/archive/2026-05-11-stuf-support/tasks.md
similarity index 100%
rename from openspec/changes/stuf-support/tasks.md
rename to openspec/changes/archive/2026-05-11-stuf-support/tasks.md
diff --git a/openspec/changes/task-management/.openspec.yaml b/openspec/changes/archive/2026-05-11-task-management/.openspec.yaml
similarity index 100%
rename from openspec/changes/task-management/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-task-management/.openspec.yaml
diff --git a/openspec/changes/task-management/builds/build.json b/openspec/changes/archive/2026-05-11-task-management/builds/build.json
similarity index 100%
rename from openspec/changes/task-management/builds/build.json
rename to openspec/changes/archive/2026-05-11-task-management/builds/build.json
diff --git a/openspec/changes/task-management/design.md b/openspec/changes/archive/2026-05-11-task-management/design.md
similarity index 100%
rename from openspec/changes/task-management/design.md
rename to openspec/changes/archive/2026-05-11-task-management/design.md
diff --git a/openspec/changes/task-management/pipeline-logs/build.1.jsonl.gz b/openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.1.jsonl.gz
similarity index 100%
rename from openspec/changes/task-management/pipeline-logs/build.1.jsonl.gz
rename to openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.1.jsonl.gz
diff --git a/openspec/changes/task-management/pipeline-logs/build.2.jsonl.gz b/openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.2.jsonl.gz
similarity index 100%
rename from openspec/changes/task-management/pipeline-logs/build.2.jsonl.gz
rename to openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.2.jsonl.gz
diff --git a/openspec/changes/task-management/pipeline-logs/build.3.jsonl.gz b/openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.3.jsonl.gz
similarity index 100%
rename from openspec/changes/task-management/pipeline-logs/build.3.jsonl.gz
rename to openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.3.jsonl.gz
diff --git a/openspec/changes/task-management/pipeline-logs/build.4.jsonl.gz b/openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.4.jsonl.gz
similarity index 100%
rename from openspec/changes/task-management/pipeline-logs/build.4.jsonl.gz
rename to openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.4.jsonl.gz
diff --git a/openspec/changes/task-management/pipeline-logs/build.jsonl.gz b/openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.jsonl.gz
similarity index 100%
rename from openspec/changes/task-management/pipeline-logs/build.jsonl.gz
rename to openspec/changes/archive/2026-05-11-task-management/pipeline-logs/build.jsonl.gz
diff --git a/openspec/changes/task-management/proposal.md b/openspec/changes/archive/2026-05-11-task-management/proposal.md
similarity index 100%
rename from openspec/changes/task-management/proposal.md
rename to openspec/changes/archive/2026-05-11-task-management/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-task-management/specs/task-management/spec.md b/openspec/changes/archive/2026-05-11-task-management/specs/task-management/spec.md
new file mode 100644
index 00000000..23b27c1b
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-task-management/specs/task-management/spec.md
@@ -0,0 +1,134 @@
+---
+status: implemented
+---
+# task-management Specification
+
+## Purpose
+Define the MVP task surface in Procest: validated task creation against a parent case, lifecycle state transitions with explicit error feedback, a filterable and searchable task list, overdue highlighting, anatomy of task cards, and reliable completion-date handling. Tasks are first-class work items linked to cases and surfaced both in `TaskList.vue`/`TaskDetail.vue` and in the `my-work` view.
+
+## Context
+Procest tasks are OpenRegister objects with a parent case reference, an assignee, a status (`new`/`in-progress`/`blocked`/`done`/`cancelled`), an optional due date, and a priority. Earlier changes shipped the schema and the basic CRUD; this change closes MVP gaps in validation, lifecycle feedback, list ergonomics, and card anatomy.
+
+## ADDED Requirements
+### Requirement: REQ-TASK-VAL-01 — Case Reference Validation on Task Creation
+Task creation MUST validate that the parent case reference exists and is usable before persisting the task.
+
+#### Scenario: TASK-VAL-01-1: Missing case reference
+- **GIVEN** a user opens `TaskCreateDialog.vue` and submits without selecting a case
+- **WHEN** `taskValidation.js` runs
+- **THEN** the form MUST highlight the case field with the error "Selecteer een zaak"
+- **AND** save MUST be blocked
+
+#### Scenario: TASK-VAL-01-2: Unknown case reference
+- **GIVEN** a user submits a task referencing a case id that does not resolve via the object store
+- **THEN** validation MUST fail with "Zaak niet gevonden"
+- **AND** no task object MUST be persisted
+
+#### Scenario: TASK-VAL-01-3: Required core fields
+- **GIVEN** a task with no title
+- **THEN** validation MUST fail with "Titel is verplicht" before any backend call is made
+
+### Requirement: REQ-TASK-LC-01 — Lifecycle Transition Error Feedback
+`TaskDetail.vue` MUST surface explicit error messages when a lifecycle status transition is rejected.
+
+#### Scenario: TASK-LC-01-1: Invalid transition
+- **GIVEN** a task currently in status `done`
+- **WHEN** the user attempts to set the status back to `new`
+- **THEN** the UI MUST display a Dutch-language error explaining that the transition is not allowed
+- **AND** the task status MUST remain unchanged
+
+#### Scenario: TASK-LC-01-2: Backend rejection surfaced
+- **GIVEN** the backend returns a 4xx error on a status update
+- **THEN** the error message from the response MUST be surfaced near the status control
+- **AND** the status select MUST revert to its previous value
+
+#### Scenario: TASK-LC-01-3: Transition success clears prior errors
+- **WHEN** a transition succeeds after a prior failure
+- **THEN** any previously displayed transition error MUST be cleared
+
+### Requirement: REQ-TASK-LIST-01 — Task List Filters and Search
+`TaskList.vue` MUST expose filters for status, assignee, and priority, plus keyword search.
+
+#### Scenario: TASK-LIST-01-1: Filter by status
+- **GIVEN** the task list contains tasks in multiple statuses
+- **WHEN** the user selects a status from the status filter
+- **THEN** the list MUST refresh with `_filters[status]` applied
+- **AND** only matching tasks MUST be visible
+
+#### Scenario: TASK-LIST-01-2: Filter by assignee
+- **WHEN** the user selects an assignee from the assignee filter
+- **THEN** the list MUST refresh with `_filters[assignee]` applied
+
+#### Scenario: TASK-LIST-01-3: Filter by priority
+- **WHEN** the user selects a priority value
+- **THEN** the list MUST refresh with `_filters[priority]` applied
+
+#### Scenario: TASK-LIST-01-4: Keyword search
+- **GIVEN** a task with title "Documenten verzamelen voor advies"
+- **WHEN** the user types "advies" into the search field
+- **THEN** the list MUST refresh with `_search=advies` and the matching task MUST appear
+
+### Requirement: REQ-TASK-LIST-02 — Overdue Highlighting
+`TaskList.vue` MUST visually highlight overdue tasks.
+
+#### Scenario: TASK-LIST-02-1: Overdue row styling
+- **GIVEN** a task with a `dueDate` in the past and a non-final status
+- **THEN** the row MUST render with an explicit overdue visual treatment (red accent or icon)
+- **AND** the due date MUST be labeled "Te laat" or equivalent Dutch term
+
+#### Scenario: TASK-LIST-02-2: Completed tasks not flagged
+- **GIVEN** a task that is in status `done` or `cancelled` even with a past `dueDate`
+- **THEN** no overdue styling MUST be applied
+
+### Requirement: REQ-TASK-CARD-01 — Task Card Anatomy
+Task cards in both the list and the my-work view MUST display a consistent, recognizable anatomy.
+
+#### Scenario: TASK-CARD-01-1: Required card content
+- **GIVEN** any task card
+- **THEN** the card MUST display: title, parent case reference (identifier + truncated case title), assignee, priority indicator, status pill, and due date
+
+#### Scenario: TASK-CARD-01-2: Missing case reference
+- **GIVEN** a task whose parent case can not be resolved at render time
+- **THEN** the case reference area MUST show a neutral placeholder rather than an empty space, without breaking the layout
+
+#### Scenario: TASK-CARD-01-3: Clickable card navigates to task detail
+- **WHEN** the user clicks anywhere on the card (outside interactive controls)
+- **THEN** the app MUST navigate to the task detail view
+
+### Requirement: REQ-TASK-COMP-01 — Completion Date on Completion
+Tasks transitioning to `done` MUST have their `completedDate` set automatically.
+
+#### Scenario: TASK-COMP-01-1: Auto-set completedDate
+- **GIVEN** a task transitioning from any status to `done`
+- **WHEN** the update is persisted
+- **THEN** the task object MUST contain a `completedDate` set to the transition timestamp
+- **AND** the value MUST be set even if the client did not explicitly send it
+
+#### Scenario: TASK-COMP-01-2: Re-opening clears completion
+- **GIVEN** a task previously in `done`
+- **WHEN** the user reopens it via an allowed transition (e.g., to `in-progress`)
+- **THEN** `completedDate` MUST be cleared on the persisted object
+
+#### Scenario: TASK-COMP-01-3: Cancelled tasks
+- **GIVEN** a task transitioning to `cancelled`
+- **THEN** `completedDate` MUST NOT be set (only `done` transitions trigger it)
+
+### Requirement: REQ-TASK-INT-01 — Integration with Case Detail and My Work
+Task changes MUST stay coherent with the case detail and my-work surfaces.
+
+#### Scenario: TASK-INT-01-1: Reflected on case detail
+- **GIVEN** a task linked to a case
+- **WHEN** the task is created, updated, or completed
+- **THEN** the case detail tasks panel MUST reflect the change on next render
+
+#### Scenario: TASK-INT-01-2: Reflected in my-work
+- **GIVEN** a task assigned to the current user
+- **WHEN** the task transitions, becomes overdue, or completes
+- **THEN** the my-work view MUST reflect the change on next render and re-apply its grouping
+
+## Dependencies
+- OpenRegister `task` schema and shared `createObjectStore`
+- `src/views/tasks/TaskList.vue`, `src/views/tasks/TaskDetail.vue`, `src/views/tasks/TaskCreateDialog.vue`
+- `src/utils/taskValidation.js`
+- `case-management` capability (parent case references)
+- `my-work` capability (task surface for assigned tasks)
diff --git a/openspec/changes/task-management/tasks.md b/openspec/changes/archive/2026-05-11-task-management/tasks.md
similarity index 100%
rename from openspec/changes/task-management/tasks.md
rename to openspec/changes/archive/2026-05-11-task-management/tasks.md
diff --git a/openspec/changes/werkvoorraad/.openspec.yaml b/openspec/changes/archive/2026-05-11-werkvoorraad/.openspec.yaml
similarity index 100%
rename from openspec/changes/werkvoorraad/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-werkvoorraad/.openspec.yaml
diff --git a/openspec/changes/werkvoorraad/builds/build.json b/openspec/changes/archive/2026-05-11-werkvoorraad/builds/build.json
similarity index 100%
rename from openspec/changes/werkvoorraad/builds/build.json
rename to openspec/changes/archive/2026-05-11-werkvoorraad/builds/build.json
diff --git a/openspec/changes/werkvoorraad/delta-spec.md b/openspec/changes/archive/2026-05-11-werkvoorraad/delta-spec.md
similarity index 100%
rename from openspec/changes/werkvoorraad/delta-spec.md
rename to openspec/changes/archive/2026-05-11-werkvoorraad/delta-spec.md
diff --git a/openspec/changes/werkvoorraad/design.md b/openspec/changes/archive/2026-05-11-werkvoorraad/design.md
similarity index 100%
rename from openspec/changes/werkvoorraad/design.md
rename to openspec/changes/archive/2026-05-11-werkvoorraad/design.md
diff --git a/openspec/changes/werkvoorraad/proposal.md b/openspec/changes/archive/2026-05-11-werkvoorraad/proposal.md
similarity index 100%
rename from openspec/changes/werkvoorraad/proposal.md
rename to openspec/changes/archive/2026-05-11-werkvoorraad/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-werkvoorraad/specs/werkvoorraad/spec.md b/openspec/changes/archive/2026-05-11-werkvoorraad/specs/werkvoorraad/spec.md
new file mode 100644
index 00000000..4c517c1f
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-werkvoorraad/specs/werkvoorraad/spec.md
@@ -0,0 +1,447 @@
+---
+status: implemented
+---
+# Werkvoorraad (Work Queue) Specification
+
+## Purpose
+
+Werkvoorraad extends the existing My Work personal view with team-level work queue management. While My Work (`../my-work/spec.md`) shows a single user's assigned cases and tasks, Werkvoorraad provides team leads and managers with oversight of the full team workload: unassigned cases, workload distribution, bottlenecks, and reassignment capabilities.
+
+**Tender demand**: 23% of tenders (16/69) explicitly require werkvoorraadlijsten and team overview. The capability is also implicit in the 86% that require "gebruikersbeheer en autorisatie" -- managers need to see and manage their team's work.
+**Relationship to existing specs**: This spec EXTENDS `my-work` (personal view) and `task-management` (individual tasks). It does NOT replace them. Werkvoorraad adds the team/management layer on top.
+**Standards**: CMMN 1.1 (work queue patterns), BPMN 2.0 (resource allocation), GEMMA werkvoorraad referentiecomponent
+**Feature tier**: V1 (team overview, unassigned queue, reassignment), V2 (workload balancing, capacity planning, SLA monitoring)
+
+**Competitive context**: Dimpact ZAC implements werkvoorraad as a Solr-backed worklist with separate routes for "mijn zaken" and "werkvoorraad zaken" (unassigned cases in user's groups). ZAC uses Keycloak groups for team scoping and OPA policies for access control. Flowable provides configurable work queues via CMMN case plan models with role-based task assignment. Procest should leverage Nextcloud groups for team scoping, avoiding the need for separate identity infrastructure.
+
+## ADDED Requirements
+---
+
+### Requirement: REQ-WV-01 — Team Work Queue View
+
+The system MUST provide a team work queue showing all open cases and tasks for a team or afdeling, accessible via a dedicated navigation entry.
+
+**Feature tier**: V1
+
+#### Scenario: Team overview for manager
+
+- **GIVEN** manager "Teamleider Vergunningen" responsible for team members ["Jan", "Maria", "Pieter", "Anouk"]
+- **AND** the team has 24 open cases and 45 active tasks distributed across members
+- **WHEN** the teamleider views the Werkvoorraad
+- **THEN** the system MUST display:
+ - Total open cases (24) and tasks (45) for the team
+ - Per-member breakdown: Jan (8 cases, 12 tasks), Maria (6, 11), Pieter (5, 10), Anouk (5, 12)
+ - Unassigned cases (0) and tasks (0)
+- **AND** the teamleider MUST be able to click on a member to see their specific items
+- **AND** the view MUST load within 3 seconds for teams of up to 20 members
+
+#### Scenario: Unassigned work queue
+
+- **GIVEN** 3 cases and 5 tasks that have no assignee
+- **AND** the cases belong to case types managed by the team "Vergunningen"
+- **WHEN** the teamleider views the "Niet-toegewezen" tab
+- **THEN** all unassigned items MUST be listed
+- **AND** items MUST be sorted by deadline (nearest first)
+- **AND** the teamleider MUST be able to assign items to team members from this view via a dropdown
+
+#### Scenario: Cross-team manager view
+
+- **GIVEN** a manager responsible for multiple teams (Vergunningen + Toezicht)
+- **WHEN** the manager opens the werkvoorraad
+- **THEN** the system MUST show a team selector allowing switching between teams
+- **AND** an "Alle teams" option MUST aggregate all teams the manager is responsible for
+- **AND** team totals MUST be visible in the selector: "Vergunningen (24 zaken)" and "Toezicht (18 zaken)"
+
+#### Scenario: No team membership
+
+- **GIVEN** a user who is not a teamleider and has no werkvoorraad access
+- **WHEN** the user navigates to the werkvoorraad URL
+- **THEN** the system MUST show an access denied message: "U heeft geen toegang tot de werkvoorraad. Neem contact op met uw beheerder."
+- **AND** the navigation item MUST NOT appear for users without werkvoorraad permissions
+
+#### Scenario: Empty team queue
+
+- **GIVEN** a team with no open cases or tasks
+- **WHEN** the teamleider views the werkvoorraad
+- **THEN** the system MUST display an empty state: "Geen openstaande zaken of taken voor dit team"
+- **AND** KPI cards MUST show zeros for all metrics
+
+---
+
+### Requirement: REQ-WV-02 — Team Scoping via Nextcloud Groups
+
+The system MUST use Nextcloud groups as the foundation for team scoping. A group is a team when it is configured as such in Procest admin settings.
+
+**Feature tier**: V1
+
+#### Scenario: Configure team from Nextcloud group
+
+- **GIVEN** Nextcloud groups "afd-vergunningen" (10 members) and "afd-toezicht" (8 members) exist
+- **WHEN** the beheerder opens Procest admin settings and configures "afd-vergunningen" as a werkvoorraad team
+- **THEN** the system MUST store the group ID as a team configuration
+- **AND** case types MUST be assignable to this team
+- **AND** members of "afd-vergunningen" MUST appear in the werkvoorraad member list
+
+#### Scenario: Teamleider role assignment
+
+- **GIVEN** group "afd-vergunningen" is configured as a team
+- **WHEN** the beheerder assigns user "Jan de Vries" as teamleider
+- **THEN** Jan MUST have access to the werkvoorraad for "afd-vergunningen"
+- **AND** Jan MUST be able to assign and reassign items within the team
+- **AND** regular team members MUST NOT see the werkvoorraad (only My Work)
+
+#### Scenario: Dynamic group membership
+
+- **GIVEN** "Maria" is added to Nextcloud group "afd-vergunningen"
+- **WHEN** the werkvoorraad is next loaded
+- **THEN** Maria MUST appear in the member list
+- **AND** any unassigned cases for this team's case types MUST be assignable to Maria
+
+---
+
+### Requirement: REQ-WV-03 — Priority and Urgency Sorting
+
+The work queue MUST support sorting by priority, urgency (deadline proximity), and a combined urgency score that weighs both factors.
+
+**Feature tier**: V1
+
+#### Scenario: Urgency-based sorting
+
+- **GIVEN** a work queue with items:
+ - Case A: priority high, deadline tomorrow
+ - Case B: priority normal, deadline overdue by 3 days
+ - Case C: priority urgent, deadline in 10 days
+ - Case D: priority normal, no deadline
+- **WHEN** sorted by urgency (default)
+- **THEN** the order MUST be: B (overdue), A (due tomorrow + high priority), C (urgent), D (no deadline)
+
+#### Scenario: Sort by handler workload
+
+- **GIVEN** 4 team members with varying workloads: Jan (12 items), Maria (6 items), Pieter (9 items), Anouk (3 items)
+- **WHEN** the teamleider sorts by "Werklast" (workload)
+- **THEN** members MUST be ordered by total active items descending: Jan (12), Pieter (9), Maria (6), Anouk (3)
+- **AND** a visual bar chart or indicator MUST show relative workload
+
+#### Scenario: Sort by days in current status
+
+- **GIVEN** 3 cases in status "In behandeling" for different durations
+ - Case X: 2 days in current status
+ - Case Y: 15 days in current status
+ - Case Z: 8 days in current status
+- **WHEN** the teamleider sorts by "Dagen in huidige status"
+- **THEN** the order MUST be: Y (15), Z (8), X (2)
+- **AND** cases exceeding the average processing time for their type MUST be highlighted
+
+---
+
+### Requirement: REQ-WV-04 — Filters and Views
+
+The work queue MUST support filtering by zaaktype, status, afdeling, handler, deadline range, and priority.
+
+**Feature tier**: V1
+
+#### Scenario: Filter by zaaktype
+
+- **GIVEN** a team handling cases of types "Omgevingsvergunning" (10), "Sloopmelding" (8), "Milieumelding" (6)
+- **WHEN** the teamleider filters by "Omgevingsvergunning"
+- **THEN** only the 10 omgevingsvergunning cases MUST be shown
+- **AND** the filter MUST show counts per zaaktype for quick selection
+
+#### Scenario: Filter overdue items
+
+- **GIVEN** 5 cases past their deadline
+- **WHEN** the teamleider selects filter "Verlopen termijn"
+- **THEN** only the 5 overdue cases MUST be shown
+- **AND** each case MUST show the number of days overdue with severity coloring: 1-3 days (orange), 4+ days (red)
+
+#### Scenario: Filter by handler
+
+- **GIVEN** the teamleider wants to see all work for "Pieter"
+- **WHEN** selecting handler filter "Pieter"
+- **THEN** only Pieter's cases and tasks MUST be shown
+- **AND** the view MUST include both active and recently completed items (last 7 days)
+
+#### Scenario: Combine multiple filters
+
+- **GIVEN** filters: zaaktype = "Omgevingsvergunning", status = "In behandeling", handler = unassigned
+- **WHEN** all three filters are active simultaneously
+- **THEN** only unassigned omgevingsvergunning cases in "In behandeling" status MUST be shown
+- **AND** the active filters MUST be displayed as removable chips above the list
+
+#### Scenario: Save filter preset
+
+- **GIVEN** the teamleider has configured a useful filter combination
+- **WHEN** the teamleider clicks "Opslaan als weergave" and names it "Urgente vergunningen"
+- **THEN** the preset MUST be stored per user
+- **AND** the preset MUST appear in a quick-access dropdown for future use
+
+---
+
+### Requirement: REQ-WV-05 — Bulk Reassignment
+
+The system MUST support reassigning multiple cases or tasks at once, for example when a team member is absent.
+
+**Feature tier**: V1
+
+#### Scenario: Reassign all work from absent colleague
+
+- **GIVEN** Maria is on sick leave with 6 open cases and 11 tasks
+- **WHEN** the teamleider selects all of Maria's items and clicks "Herverdelen"
+- **THEN** the system MUST allow selecting a target assignee (or "round-robin" across team)
+- **AND** all selected items MUST be reassigned
+- **AND** each reassignment MUST be recorded in the audit trail: "Herverdeeld van Maria naar [target] door [teamleider], reden: afwezigheid"
+
+#### Scenario: Round-robin distribution
+
+- **GIVEN** 8 unassigned cases to distribute across 4 team members [Jan, Maria, Pieter, Anouk]
+- **AND** current workloads are: Jan (5), Maria (3), Pieter (4), Anouk (2)
+- **WHEN** the teamleider selects "Gelijkmatig verdelen" (balanced distribution)
+- **THEN** the system MUST distribute cases to balance workloads: Anouk gets 3, Maria gets 2, Pieter gets 2, Jan gets 1
+- **AND** each assignment MUST be recorded in the audit trail
+
+#### Scenario: Partial reassignment
+
+- **GIVEN** the teamleider selects 3 of Maria's 6 cases using checkboxes
+- **WHEN** clicking "Toewijzen aan" and selecting "Pieter"
+- **THEN** only the 3 selected cases MUST be reassigned to Pieter
+- **AND** Maria MUST retain the other 3 cases
+
+#### Scenario: Reassignment with reason
+
+- **GIVEN** the teamleider reassigns cases from Maria to Pieter
+- **WHEN** the reassignment dialog opens
+- **THEN** a reason field MUST be presented (optional but recommended)
+- **AND** common reasons MUST be available as quick-select: "Afwezigheid", "Capaciteit", "Expertise", "Anders"
+- **AND** the reason MUST be stored in the audit trail
+
+---
+
+### Requirement: REQ-WV-06 — Deadline Monitoring and Escalation
+
+The system MUST actively monitor deadlines and alert when cases approach or pass their deadline.
+
+**Feature tier**: V1
+
+#### Scenario: Deadline warning notification
+
+- **GIVEN** a case with deadline in 3 days and no status change in the last 5 days
+- **WHEN** the daily deadline check runs (Nextcloud background job)
+- **THEN** the handler AND the teamleider MUST receive a Nextcloud notification: "Zaak [identifier] nadert deadline (nog 3 dagen)"
+- **AND** the notification MUST link to the case detail page
+
+#### Scenario: Overdue escalation
+
+- **GIVEN** a case 2 days past its deadline
+- **WHEN** the daily deadline check runs
+- **THEN** the teamleider MUST receive an escalation notification: "Zaak [identifier] is 2 dagen over de termijn"
+- **AND** the case MUST appear in the "Verlopen" section of the werkvoorraad with a red indicator
+- **AND** the notification MUST NOT be sent again for the same case on subsequent days (only on day 1 overdue)
+
+#### Scenario: Configurable warning thresholds
+
+- **GIVEN** the beheerder configures warning thresholds per case type:
+ - Omgevingsvergunning: warn at 5 days before deadline
+ - Sloopmelding: warn at 3 days before deadline
+- **WHEN** a case approaches the configured threshold
+- **THEN** the notification MUST fire at the configured interval, not a global default
+
+#### Scenario: SLA indicator on queue items
+
+- **GIVEN** a case type "Omgevingsvergunning" with processing deadline of 8 weeks
+- **AND** a case has been open for 6 weeks (75% of deadline consumed)
+- **WHEN** the case appears in the werkvoorraad
+- **THEN** it MUST display a progress indicator showing 75% time consumed
+- **AND** the indicator MUST use color coding: green (0-60%), orange (60-85%), red (85-100%), dark red (>100%)
+
+#### Scenario: Weekly deadline summary
+
+- **GIVEN** a team with 3 cases due this week and 2 cases overdue
+- **WHEN** Monday morning arrives (configurable day)
+- **THEN** the teamleider MUST receive a summary notification: "Werkvoorraad weekoverzicht: 3 zaken deze week, 2 verlopen"
+- **AND** the summary MUST be optional (configurable in user settings)
+
+---
+
+### Requirement: REQ-WV-07 — Werkvoorraad Dashboard KPIs
+
+The werkvoorraad MUST display key performance indicators at the top of the view, providing at-a-glance team health metrics.
+
+**Feature tier**: V1
+
+#### Scenario: Team KPI cards
+
+- **GIVEN** a team with 24 open cases, 3 overdue, 12 completed this week, average processing time 14 days
+- **WHEN** the teamleider views the werkvoorraad
+- **THEN** KPI cards MUST display: "Open zaken" (24), "Verlopen" (3, in red), "Afgehandeld deze week" (12), "Gem. doorlooptijd" (14 dagen)
+- **AND** each card MUST be clickable to filter the queue to that subset
+
+#### Scenario: Trend indicators
+
+- **GIVEN** last week the team had 28 open cases and this week 24
+- **WHEN** the KPI cards render
+- **THEN** the "Open zaken" card MUST show a trend arrow: down arrow with "-4" indicating improvement
+- **AND** the "Verlopen" card MUST show trend compared to last week
+
+#### Scenario: Case type distribution
+
+- **GIVEN** the team handles 3 case types with varying volumes
+- **WHEN** the teamleider views the werkvoorraad dashboard
+- **THEN** a distribution chart MUST show: "Omgevingsvergunning" (10), "Sloopmelding" (8), "Milieumelding" (6)
+- **AND** the chart MUST use the existing `StatusChart.vue` component pattern
+
+---
+
+### Requirement: REQ-WV-08 — Workload Statistics
+
+The system SHALL provide workload statistics for capacity planning and performance monitoring.
+
+**Feature tier**: V2
+
+#### Scenario: Team capacity overview
+
+- **GIVEN** a team of 4 members with varying workloads
+- **WHEN** the teamleider views the capacity overview
+- **THEN** the system MUST show per member: open cases count, active tasks count, average processing time, cases completed this week
+- **AND** members with significantly above-average workload (>150% of mean) MUST be highlighted in orange
+
+#### Scenario: Historical throughput
+
+- **GIVEN** the team has been active for 3 months
+- **WHEN** the teamleider views the "Doorlooptijd" (throughput) tab
+- **THEN** the system MUST show a chart with: cases opened per week, cases closed per week, average backlog size
+- **AND** the chart MUST allow selecting different time ranges: 1 week, 1 month, 3 months, 6 months
+
+#### Scenario: Per-case-type processing time
+
+- **GIVEN** case type "Omgevingsvergunning" has a legal deadline of 8 weeks
+- **AND** the team's average processing time for this type is 5.2 weeks
+- **WHEN** the teamleider views case type statistics
+- **THEN** the system MUST show: average processing time (5.2 weeks), legal deadline (8 weeks), percentage within deadline (92%), number completed (48)
+
+---
+
+### Requirement: REQ-WV-09 — Export and Reporting
+
+The werkvoorraad MUST support exporting filtered queue data for external reporting and management meetings.
+
+**Feature tier**: V2
+
+#### Scenario: CSV export of current view
+
+- **GIVEN** the teamleider has filtered the werkvoorraad to show overdue cases
+- **WHEN** clicking the "Exporteren" button
+- **THEN** the system MUST generate a CSV file with columns: zaakidentificatie, titel, zaaktype, status, behandelaar, startdatum, deadline, dagen verlopen
+- **AND** the export MUST respect the current filter and sort order
+
+#### Scenario: PDF summary report
+
+- **GIVEN** the teamleider wants a weekly report for management
+- **WHEN** clicking "Rapport genereren"
+- **THEN** the system MUST generate a PDF containing: KPI summary, overdue cases list, workload distribution per member, case type distribution chart
+- **AND** the report MUST include the date range and team name
+
+---
+
+### Requirement: REQ-WV-10 — Real-Time Updates
+
+The werkvoorraad SHALL update in near-real-time when cases are created, assigned, or status-changed by other users.
+
+**Feature tier**: V2
+
+#### Scenario: Case assigned by another user
+
+- **GIVEN** the teamleider has the werkvoorraad open showing 3 unassigned cases
+- **AND** another behandelaar assigns one of those cases to themselves via the case detail page
+- **WHEN** the werkvoorraad refreshes (polling every 30 seconds or WebSocket)
+- **THEN** the unassigned count MUST update from 3 to 2
+- **AND** the assigned case MUST move to the correct team member's column
+
+#### Scenario: New case created
+
+- **GIVEN** a new case is created via DSO intake that matches the team's case types
+- **WHEN** the werkvoorraad refreshes
+- **THEN** the new case MUST appear in the unassigned queue
+- **AND** the "Open zaken" KPI MUST increment
+
+---
+
+### Requirement: REQ-WV-11 — Accessibility
+
+The werkvoorraad MUST meet WCAG AA accessibility requirements.
+
+**Feature tier**: V1
+
+#### Scenario: Screen reader navigation
+
+- **GIVEN** a screen reader user navigating the werkvoorraad
+- **THEN** all interactive elements (tabs, filters, assignment dropdowns, checkboxes) MUST have appropriate ARIA labels
+- **AND** the KPI cards MUST announce their values: "Open zaken: 24"
+- **AND** table rows MUST be navigable via keyboard (Tab/Arrow keys)
+
+#### Scenario: Keyboard bulk selection
+
+- **GIVEN** a keyboard-only user viewing the werkvoorraad
+- **WHEN** pressing Space on a row to select it, then Shift+Space to extend selection
+- **THEN** the selection MUST work identically to mouse-based checkbox selection
+- **AND** the "Herverdelen" action MUST be triggerable via keyboard
+
+## Dependencies
+
+- **My Work spec** (`../my-work/spec.md`): Personal view; Werkvoorraad adds the team layer.
+- **Task Management spec** (`../task-management/spec.md`): Tasks are the atomic work items.
+- **Case Management spec** (`../case-management/spec.md`): Cases are the primary work units.
+- **Admin Settings spec** (`../admin-settings/spec.md`): Team/afdeling configuration, warning thresholds.
+- **Dashboard spec** (`../dashboard/spec.md`): Shared KPI card and chart components.
+- **OpenRegister**: All queries against `procest` register, filtered by assignee and team membership.
+- **Nextcloud Groups**: Team scoping uses Nextcloud's `IGroupManager` for group membership queries.
+
+---
+
+### Current Implementation Status
+
+**Not implemented as a team-level feature.** The personal My Work view exists (`src/views/MyWork.vue`) but no team/manager werkvoorraad functionality has been built.
+
+**Existing foundations that relate to this spec:**
+- **My Work view**: `src/views/MyWork.vue` -- personal workload view showing cases and tasks assigned to the current user. Includes grouping by urgency (overdue, due this week, upcoming, no deadline), filter tabs (all/cases/tasks), overdue highlighting, and show-completed toggle. This is the base UI pattern that werkvoorraad extends with team oversight.
+- **Dashboard widgets**: `lib/Dashboard/CasesOverviewWidget.php` and `src/views/widgets/CasesOverviewWidget.vue` -- shows case overview, could be extended for team statistics. `lib/Dashboard/OverdueCasesWidget.php` shows overdue cases with severity-based coloring.
+- **Dashboard panels**: `src/views/dashboard/KpiCards.vue` shows KPI summary cards (open cases, new today, overdue, completed this month, avg processing days, my tasks, tasks due today). `src/views/dashboard/StatusChart.vue` shows status distribution. `src/views/dashboard/OverduePanel.vue` shows overdue items with days-overdue count.
+- **Object store**: The `useObjectStore()` can filter by `assignee` and `status`, which could be used for team member workload queries.
+- **Case assignee field**: Cases have an `assignee` field (Nextcloud user UID) in the `case` schema, enabling per-user workload queries.
+- **Task assignee field**: Tasks have an `assignee` field, enabling per-user task queries.
+- **Notification service**: `lib/Service/NotificatieService.php` provides notification infrastructure for deadline alerts.
+
+**Not yet implemented:**
+- **REQ-WV-01: Team work queue view**: No team overview, no per-member breakdown, no unassigned items queue.
+- **REQ-WV-02: Team scoping**: No concept of teams or departments (afdelingen) in the data model. Nextcloud groups exist but are not used for team scoping.
+- **REQ-WV-03: Priority/urgency sorting**: The My Work view has urgency grouping, but no combined urgency score or team-level sorting.
+- **REQ-WV-04: Filters and views**: No zaaktype filter, afdeling filter, or deadline range filter on a team-level view.
+- **REQ-WV-05: Bulk reassignment**: No multi-select or bulk reassignment capability. Only individual handler reassignment exists in `ParticipantsSection.vue`.
+- **REQ-WV-06: Deadline monitoring**: No automated daily deadline checks or notification triggers. Overdue display is purely visual (client-side calculation in `OverduePanel.vue`).
+- **REQ-WV-07: Werkvoorraad KPIs**: Dashboard KPIs exist but are personal-scoped, not team-scoped.
+- **REQ-WV-08: Workload statistics (V2)**: No capacity overview, average processing time, or workload comparison.
+- **REQ-WV-09: Export (V2)**: No CSV or PDF export functionality.
+- **REQ-WV-10: Real-time updates (V2)**: No polling or WebSocket for live updates.
+- **REQ-WV-11: Accessibility**: Existing My Work view needs WCAG audit.
+
+### Standards & References
+
+- **CMMN 1.1**: Work queue patterns for distributing and managing case work items.
+- **BPMN 2.0**: Resource allocation patterns for work distribution.
+- **ZGW APIs**: The ZGW model does not define a werkvoorraad concept directly, but team-based case filtering is standard in Dutch zaaksystemen. Dimpact ZAC implements separate `/zaken/werkvoorraad` and `/zaken/mijn` worklist routes.
+- **GEMMA**: Werkvoorraad is a standard component in the GEMMA reference architecture for zaakgericht werken.
+- **Common Ground**: The werkvoorraad is typically a process-layer concern built on top of the ZGW information layer.
+- **BIO**: Audit trail requirements for reassignment actions (who reassigned, when, why).
+- **WCAG 2.1 AA**: Accessibility requirements for government applications.
+- **Nextcloud OCP**: `IGroupManager` for group membership queries, `INotificationManager` for deadline notifications.
+
+### Specificity Assessment
+
+- **V1 requirements are well-specified** with concrete scenarios for team overview, team scoping, filtering, bulk reassignment, deadline monitoring, and KPIs.
+- **Team scoping is now defined**: Teams are Nextcloud groups configured as werkvoorraad teams in admin settings. Teamleiders are designated users with werkvoorraad access.
+- **Resolved open questions:**
+ - Teams are defined via Nextcloud groups (REQ-WV-02).
+ - Access is controlled by teamleider role assignment (REQ-WV-02b).
+ - Werkvoorraad is a separate navigation item, not a tab in My Work (REQ-WV-01).
+ - Notifications use Nextcloud background jobs (REQ-WV-06a).
+ - Team membership is determined by Nextcloud group membership (REQ-WV-02c).
+ - Round-robin distribution uses workload-aware balancing (REQ-WV-05b).
diff --git a/openspec/changes/werkvoorraad/tasks.md b/openspec/changes/archive/2026-05-11-werkvoorraad/tasks.md
similarity index 100%
rename from openspec/changes/werkvoorraad/tasks.md
rename to openspec/changes/archive/2026-05-11-werkvoorraad/tasks.md
diff --git a/openspec/changes/workflow-definition-model/builds/build.json b/openspec/changes/archive/2026-05-11-workflow-definition-model/builds/build.json
similarity index 100%
rename from openspec/changes/workflow-definition-model/builds/build.json
rename to openspec/changes/archive/2026-05-11-workflow-definition-model/builds/build.json
diff --git a/openspec/changes/workflow-definition-model/design.md b/openspec/changes/archive/2026-05-11-workflow-definition-model/design.md
similarity index 100%
rename from openspec/changes/workflow-definition-model/design.md
rename to openspec/changes/archive/2026-05-11-workflow-definition-model/design.md
diff --git a/openspec/changes/workflow-definition-model/proposal.md b/openspec/changes/archive/2026-05-11-workflow-definition-model/proposal.md
similarity index 100%
rename from openspec/changes/workflow-definition-model/proposal.md
rename to openspec/changes/archive/2026-05-11-workflow-definition-model/proposal.md
diff --git a/openspec/changes/workflow-definition-model/specs/workflow-definition-model/spec.md b/openspec/changes/archive/2026-05-11-workflow-definition-model/specs/workflow-definition-model/spec.md
similarity index 87%
rename from openspec/changes/workflow-definition-model/specs/workflow-definition-model/spec.md
rename to openspec/changes/archive/2026-05-11-workflow-definition-model/specs/workflow-definition-model/spec.md
index e027cedf..ef2bd076 100644
--- a/openspec/changes/workflow-definition-model/specs/workflow-definition-model/spec.md
+++ b/openspec/changes/archive/2026-05-11-workflow-definition-model/specs/workflow-definition-model/spec.md
@@ -11,17 +11,16 @@ Establish the declarative workflow-definition contract for Procest. A `workflowT
The `workflowTemplate` schema is in `lib/Settings/procest_register.json` and the legacy `openspec/specs/workflow-definition-model/spec.md` already describes the data shape and a seeded bezwaar/beroep workflow. What is missing is the surface that turns the schema into a usable, governed configuration object: CRUD service, lifecycle, REST endpoints, admin UI, and migration of legacy implicit lifecycles. Today, case lifecycles are implicit in `statusType` ordering and scattered button logic, which makes them impossible to test, version, or hand to a tenant admin.
-## Requirements
-
+## ADDED Requirements
---
-### REQ-WDM-1: Declarative Workflow Definition Entity
+### Requirement: REQ-WDM-1 — Declarative Workflow Definition Entity
The system SHALL expose a declarative `workflowTemplate` (aliased `WorkflowDefinition`) entity that aggregates the entire lifecycle of one `caseType`: ordered `steps[]`, allowed `transitions[]`, embedded guards, `allowedRoles`, and `automaticActions`. The entity SHALL be stored as a single OpenRegister object.
**Feature tier**: V1
-#### REQ-WDM-1-001: Definition stores steps and transitions in one object
+#### Scenario: Definition stores steps and transitions in one object
- **GIVEN** an administrator has created a workflow definition for caseType "Omgevingsvergunning"
- **WHEN** the definition is saved
@@ -30,7 +29,7 @@ The system SHALL expose a declarative `workflowTemplate` (aliased `WorkflowDefin
- **AND** every `transitions[].fromStatus` and `transitions[].toStatus` SHALL reference a `statusType` UUID that belongs to the linked `caseType`
- **AND** every `steps[].status` SHALL reference a `statusType` UUID that belongs to the linked `caseType`
-#### REQ-WDM-1-002: Definition is the single source of lifecycle truth
+#### Scenario: Definition is the single source of lifecycle truth
- **GIVEN** a case bound to a published workflow definition
- **WHEN** `status-transition-engine` or `role-based-step-routing` computes available actions for the current user
@@ -39,27 +38,27 @@ The system SHALL expose a declarative `workflowTemplate` (aliased `WorkflowDefin
---
-### REQ-WDM-2: CRUD Service
+### Requirement: REQ-WDM-2 — CRUD Service
The system SHALL provide a `WorkflowDefinitionService` exposing create, read, update, delete, and lifecycle operations for `workflowTemplate` objects. All mutations SHALL derive the actor from `IUserSession`; no caller-supplied identity SHALL be accepted.
**Feature tier**: V1
-#### REQ-WDM-2-001: Create a new draft
+#### Scenario: Create a new draft
- **GIVEN** an administrator submits a request to create a workflow definition for caseType X
- **WHEN** `WorkflowDefinitionService::createDraft($caseTypeId, $data)` is invoked
- **THEN** a `workflowTemplate` SHALL be persisted with `lifecycleStatus: draft`, `isDraft: true`, `isActive: false`, and `version` set to (max existing version for caseType X) + 1
- **AND** if no prior version exists, `version` SHALL equal 1
-#### REQ-WDM-2-002: Update a draft
+#### Scenario: Update a draft
- **GIVEN** a workflow definition with `lifecycleStatus: draft`
- **WHEN** `updateDraft($id, $data)` is invoked with new steps/transitions
- **THEN** the object SHALL be updated in OpenRegister
- **AND** referential integrity SHALL be revalidated (every status reference belongs to the linked caseType)
-#### REQ-WDM-2-003: Delete restricted to drafts
+#### Scenario: Delete restricted to drafts
- **GIVEN** an administrator attempts to delete a workflow definition
- **WHEN** the definition has `lifecycleStatus` other than `draft`
@@ -68,13 +67,13 @@ The system SHALL provide a `WorkflowDefinitionService` exposing create, read, up
---
-### REQ-WDM-3: REST API
+### Requirement: REQ-WDM-3 — REST API
The system SHALL expose authenticated REST endpoints under `/api/workflow-definition` for definition CRUD and lifecycle operations. Endpoints SHALL never return raw exception messages.
**Feature tier**: V1
-#### REQ-WDM-3-001: Endpoint contract
+#### Scenario: Endpoint contract
- **WHEN** the controller is registered
- **THEN** the following endpoints SHALL respond on the listed verbs:
@@ -87,7 +86,7 @@ The system SHALL expose authenticated REST endpoints under `/api/workflow-defini
- `POST /api/workflow-definition/{id}/deprecate` (deprecate — admin only)
- `POST /api/workflow-definition/{id}/clone` (clone into new draft)
-#### REQ-WDM-3-002: No raw exception leakage
+#### Scenario: No raw exception leakage
- **WHEN** any controller method handles a `\Throwable`
- **THEN** the response SHALL contain a static, human-readable error message
@@ -95,13 +94,13 @@ The system SHALL expose authenticated REST endpoints under `/api/workflow-defini
---
-### REQ-WDM-4: Draft → Published → Deprecated Lifecycle
+### Requirement: REQ-WDM-4 — Draft → Published → Deprecated Lifecycle
The system SHALL implement a strict three-state lifecycle for every workflow definition: `draft`, `published`, `deprecated`. Transitions between states SHALL be one-directional except for `clone` which forks a new draft.
**Feature tier**: V1
-#### REQ-WDM-4-001: Allowed lifecycle transitions
+#### Scenario: Allowed lifecycle transitions
- **GIVEN** a workflow definition in some `lifecycleStatus`
- **WHEN** a lifecycle operation is invoked
@@ -112,7 +111,7 @@ The system SHALL implement a strict three-state lifecycle for every workflow def
- `deprecated` → new `draft` (separate object) via `clone()`
- **AND** any other transition SHALL be rejected with a static error
-#### REQ-WDM-4-002: Published versions are immutable
+#### Scenario: Published versions are immutable
- **GIVEN** a workflow definition with `lifecycleStatus: published` or `lifecycleStatus: deprecated`
- **WHEN** any caller attempts `updateDraft($id, ...)` or `delete($id)`
@@ -121,13 +120,13 @@ The system SHALL implement a strict three-state lifecycle for every workflow def
---
-### REQ-WDM-5: Publish Operation
+### Requirement: REQ-WDM-5 — Publish Operation
Publishing a draft SHALL be a single atomic operation that promotes the draft, deprecates any previously active version of the same `caseType`, and re-points the `caseType.workflowDefinition` reference to the newly active version.
**Feature tier**: V1
-#### REQ-WDM-5-001: Publish atomically promotes and deprecates
+#### Scenario: Publish atomically promotes and deprecates
- **GIVEN** caseType X has workflow definition v2 with `lifecycleStatus: published, isActive: true`
- **AND** a new draft v3 exists for caseType X
@@ -137,7 +136,7 @@ Publishing a draft SHALL be a single atomic operation that promotes the draft, d
- **AND** `caseType.workflowDefinition` SHALL point at v3
- **AND** if any step in the operation fails, the system SHALL not leave both v2 and v3 active
-#### REQ-WDM-5-002: Publish validates referential integrity
+#### Scenario: Publish validates referential integrity
- **GIVEN** a draft definition whose `transitions[]` contains a `statusType` UUID that does not belong to the linked `caseType`
- **WHEN** `publish($id)` is invoked
@@ -146,13 +145,13 @@ Publishing a draft SHALL be a single atomic operation that promotes the draft, d
---
-### REQ-WDM-6: Deprecate Operation
+### Requirement: REQ-WDM-6 — Deprecate Operation
The system SHALL allow deprecating a published definition. Deprecated definitions SHALL not back new cases but SHALL continue to back existing in-flight cases.
**Feature tier**: V1
-#### REQ-WDM-6-001: Deprecate marks inactive but preserves existing cases
+#### Scenario: Deprecate marks inactive but preserves existing cases
- **GIVEN** workflow definition v2 is `published, isActive: true` for caseType X
- **AND** five open cases of caseType X are bound to v2 via `case.workflowVersion = 2`
@@ -160,7 +159,7 @@ The system SHALL allow deprecating a published definition. Deprecated definition
- **THEN** the deprecation SHALL be rejected with a static error referencing the open cases
- **AND** no field SHALL change on v2
-#### REQ-WDM-6-002: Deprecation succeeds when another published version exists
+#### Scenario: Deprecation succeeds when another published version exists
- **GIVEN** workflow definition v2 (`published, isActive: true`) and v3 (`published, isActive: false`) both exist for caseType X
- **WHEN** `publish(v3)` is invoked
@@ -169,20 +168,20 @@ The system SHALL allow deprecating a published definition. Deprecated definition
---
-### REQ-WDM-7: Versioning and Case Pinning
+### Requirement: REQ-WDM-7 — Versioning and Case Pinning
The system SHALL pin each case to a specific workflow version at the moment the case is created. New cases SHALL bind to the currently active published version; subsequent definition edits SHALL NOT affect in-flight cases.
**Feature tier**: V1
-#### REQ-WDM-7-001: Case binds to active version on creation
+#### Scenario: Case binds to active version on creation
- **GIVEN** caseType X has workflow definition v3 with `lifecycleStatus: published, isActive: true`
- **WHEN** a new case of caseType X is created
- **THEN** `case.workflowTemplate` SHALL be set to v3's UUID
- **AND** `case.workflowVersion` SHALL be set to `3`
-#### REQ-WDM-7-002: Existing cases keep their pinned version after a new publish
+#### Scenario: Existing cases keep their pinned version after a new publish
- **GIVEN** a case pinned to v3 (`case.workflowVersion = 3`)
- **AND** the administrator subsequently publishes v4
@@ -190,7 +189,7 @@ The system SHALL pin each case to a specific workflow version at the moment the
- **THEN** `getDefinitionForCase($caseId)` SHALL return v3, not v4
- **AND** the transition buttons and step visibility SHALL reflect v3's configuration
-#### REQ-WDM-7-003: CaseType.workflowDefinition pin overrides "latest published"
+#### Scenario: CaseType.workflowDefinition pin overrides "latest published"
- **GIVEN** caseType X has `workflowDefinition` set to a specific version v2 (a hand-pinned override)
- **WHEN** a new case of caseType X is created
@@ -198,27 +197,27 @@ The system SHALL pin each case to a specific workflow version at the moment the
---
-### REQ-WDM-8: Admin UI
+### Requirement: REQ-WDM-8 — Admin UI
The system SHALL provide an admin settings tab where tenant administrators can list workflow definitions per `caseType`, create drafts, edit drafts, publish, deprecate, and clone versions. All confirmations SHALL use `CnDialog` (never `window.confirm`).
**Feature tier**: V1
-#### REQ-WDM-8-001: Admin tab lists versions per caseType
+#### Scenario: Admin tab lists versions per caseType
- **GIVEN** the administrator opens "Settings → Workflows"
- **AND** selects caseType "Omgevingsvergunning" from the top selector
- **THEN** a table SHALL list every workflow definition version for that caseType
- **AND** each row SHALL show: version number, `lifecycleStatus` badge, `isActive` indicator, `updatedAt`, action buttons
-#### REQ-WDM-8-002: Actions available per lifecycleStatus
+#### Scenario: Actions available per lifecycleStatus
- **GIVEN** the rendered version table
- **THEN** rows in `draft` SHALL expose: Edit, Publish, Delete
- **AND** rows in `published` SHALL expose: Deprecate, Clone
- **AND** rows in `deprecated` SHALL expose: Clone
-#### REQ-WDM-8-003: Edit dialog supports steps and transitions
+#### Scenario: Edit dialog supports steps and transitions
- **GIVEN** the administrator clicks "Edit" on a draft
- **THEN** a dialog SHALL open with: title, description, an ordered steps editor, and a transitions editor
@@ -227,13 +226,13 @@ The system SHALL provide an admin settings tab where tenant administrators can l
---
-### REQ-WDM-9: Backfill Migration
+### Requirement: REQ-WDM-9 — Backfill Migration
The system SHALL provide a one-time idempotent migration that creates one `workflowTemplate` per existing `caseType` from the implicit ordering of its `statusType` records and pins existing open cases to that synthesised definition.
**Feature tier**: V1
-#### REQ-WDM-9-001: Backfill synthesises one definition per caseType
+#### Scenario: Backfill synthesises one definition per caseType
- **GIVEN** a tenant upgrade where caseType X has no `workflowDefinition` set
- **AND** caseType X has six `statusType` records ordered by `order`
@@ -243,13 +242,13 @@ The system SHALL provide a one-time idempotent migration that creates one `workf
- **AND** `transitions[]` SHALL contain a transition from each statusType to the next in the ordering with `guards: []` and `allowedRoles: []`
- **AND** `caseType.workflowDefinition` SHALL be set to the new template UUID
-#### REQ-WDM-9-002: Backfill pins existing open cases
+#### Scenario: Backfill pins existing open cases
- **GIVEN** caseType X has eight open cases without `workflowVersion` set when the migration runs
- **THEN** every open case SHALL receive `case.workflowTemplate` = the synthesised template UUID
- **AND** every open case SHALL receive `case.workflowVersion = 1`
-#### REQ-WDM-9-003: Backfill is idempotent
+#### Scenario: Backfill is idempotent
- **GIVEN** the repair step has already run for caseType X
- **WHEN** the same repair step runs again (e.g. next app upgrade)
@@ -258,13 +257,13 @@ The system SHALL provide a one-time idempotent migration that creates one `workf
---
-### REQ-WDM-10: Stable Consumer Contract
+### Requirement: REQ-WDM-10 — Stable Consumer Contract
The system SHALL expose a stable read-only API consumed by `status-transition-engine` and `role-based-step-routing`. Consumers SHALL never reach into OpenRegister directly to read workflow data.
**Feature tier**: V1
-#### REQ-WDM-10-001: Read API surface
+#### Scenario: Read API surface
- **WHEN** a consumer needs workflow data
- **THEN** it SHALL use exclusively one of:
@@ -274,7 +273,7 @@ The system SHALL expose a stable read-only API consumed by `status-transition-en
- `WorkflowDefinitionService::listVersions(string $caseTypeId): array`
- **AND** no consumer SHALL invoke `ObjectService::findObject(...)` on the `workflowTemplate` schema directly
-#### REQ-WDM-10-002: getDefinitionForCase resolves pinned version
+#### Scenario: getDefinitionForCase resolves pinned version
- **GIVEN** a case with `case.workflowTemplate = T` and `case.workflowVersion = 2`
- **WHEN** `getDefinitionForCase($caseId)` is invoked
diff --git a/openspec/changes/workflow-definition-model/tasks.md b/openspec/changes/archive/2026-05-11-workflow-definition-model/tasks.md
similarity index 100%
rename from openspec/changes/workflow-definition-model/tasks.md
rename to openspec/changes/archive/2026-05-11-workflow-definition-model/tasks.md
diff --git a/openspec/changes/zaak-intake-flow/.openspec.yaml b/openspec/changes/archive/2026-05-11-zaak-intake-flow/.openspec.yaml
similarity index 100%
rename from openspec/changes/zaak-intake-flow/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-zaak-intake-flow/.openspec.yaml
diff --git a/openspec/changes/zaak-intake-flow/builds/build.json b/openspec/changes/archive/2026-05-11-zaak-intake-flow/builds/build.json
similarity index 100%
rename from openspec/changes/zaak-intake-flow/builds/build.json
rename to openspec/changes/archive/2026-05-11-zaak-intake-flow/builds/build.json
diff --git a/openspec/changes/zaak-intake-flow/delta-spec.md b/openspec/changes/archive/2026-05-11-zaak-intake-flow/delta-spec.md
similarity index 100%
rename from openspec/changes/zaak-intake-flow/delta-spec.md
rename to openspec/changes/archive/2026-05-11-zaak-intake-flow/delta-spec.md
diff --git a/openspec/changes/zaak-intake-flow/design.md b/openspec/changes/archive/2026-05-11-zaak-intake-flow/design.md
similarity index 100%
rename from openspec/changes/zaak-intake-flow/design.md
rename to openspec/changes/archive/2026-05-11-zaak-intake-flow/design.md
diff --git a/openspec/changes/zaak-intake-flow/proposal.md b/openspec/changes/archive/2026-05-11-zaak-intake-flow/proposal.md
similarity index 100%
rename from openspec/changes/zaak-intake-flow/proposal.md
rename to openspec/changes/archive/2026-05-11-zaak-intake-flow/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-zaak-intake-flow/specs/zaak-intake-flow/spec.md b/openspec/changes/archive/2026-05-11-zaak-intake-flow/specs/zaak-intake-flow/spec.md
new file mode 100644
index 00000000..ac7af5e6
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-zaak-intake-flow/specs/zaak-intake-flow/spec.md
@@ -0,0 +1,509 @@
+---
+status: implemented
+---
+# Zaak Intake Flow Specification
+
+## Purpose
+
+The zaak intake flow governs what happens after a case is initiated -- whether from Open Formulieren, DSO/Omgevingsloket, manual entry, or API call. It handles automatic zaaktype assignment, status initialization, initial task creation, notification to the assigned behandelaar, and linking of the initiator. This is the bridge between external input and the internal case lifecycle.
+
+**Tender demand**: 61% of tenders (42/69) require formulieren/intake capabilities. Automatic case creation from external submissions is a baseline expectation.
+**Standards**: ZGW Zaken API (`zaak-create`), StUF-ZKN (`creeerZaak_Lk01`), CMMN 1.1 (CasePlanModel instantiation)
+**Feature tier**: MVP (manual + API intake, zaaktype assignment, status init, behandelaar notification), V1 (Open Formulieren integration, DSO intake, duplicate detection, batch intake, e-mail intake)
+
+## Intake Channels
+
+| Channel | Protocol | Description | Tier |
+|---------|----------|-------------|------|
+| Manual entry | Procest UI | Behandelaar creates case via "New Case" form | MVP |
+| ZGW API | REST (`POST /zaken/api/v1/zaken`) | External system creates case via ZGW-compliant endpoint | MVP |
+| Open Formulieren | Webhook / ZGW API | Citizen submits e-form with DigiD; form engine calls zaak-create | V1 |
+| DSO/Omgevingsloket | StUF-LVO / REST | Omgevingswet application forwarded from DSO | V1 |
+| E-mail | IMAP trigger | Incoming e-mail parsed and converted to case (via n8n) | V1 |
+| Bulk import | CSV/JSON upload | Batch case creation for migration or seasonal intake | V1 |
+
+## ADDED Requirements
+---
+
+### Requirement: REQ-INTAKE-01 — Manual Case Creation
+
+The system MUST support creating cases via the Procest UI with a guided creation form.
+
+**Feature tier**: MVP
+
+#### Scenario: Create case via dialog
+
+- **GIVEN** a user with case management access
+- **WHEN** the user clicks "+ New Case" on the dashboard or case list
+- **THEN** the system MUST display the `CaseCreateDialog` with fields: case type (dropdown), title (text), description (text area)
+- **AND** the case type dropdown MUST only show published, currently valid case types
+- **AND** the default case type (if configured) MUST be pre-selected
+
+#### Scenario: Case type selection shows metadata
+
+- **GIVEN** the case type dropdown is open
+- **WHEN** the user hovers over or selects "Omgevingsvergunning"
+- **THEN** the system SHOULD display: processing deadline ("56 days"), description, and number of required document types
+- **AND** this helps the user select the correct case type
+
+#### Scenario: Manual case submission succeeds
+
+- **GIVEN** the user has selected case type "Subsidieaanvraag" and entered title "Innovatiesubsidie 2026"
+- **WHEN** the user clicks "Create"
+- **THEN** the system MUST create the case in the `procest` register with the `case` schema
+- **AND** the `identifier` MUST be auto-generated
+- **AND** the `startDate` MUST be set to today
+- **AND** the `deadline` MUST be calculated as today + processingDeadline
+- **AND** the `status` MUST be set to the first status type by order
+- **AND** the user MUST be navigated to the new case's detail view
+
+#### Scenario: Manual case with optional description
+
+- **GIVEN** the user creates a case with only case type and title (description is empty)
+- **WHEN** the case is created
+- **THEN** the `description` field MUST be stored as empty/null
+- **AND** the case MUST be created successfully
+
+#### Scenario: Cancel case creation
+
+- **GIVEN** the case creation dialog is open
+- **WHEN** the user clicks "Cancel"
+- **THEN** the dialog MUST close without creating a case
+- **AND** no data MUST be persisted
+
+---
+
+### Requirement: REQ-INTAKE-02 — API-Driven Case Creation
+
+The system MUST accept case creation requests via the ZGW Zaken API endpoint. Upon receiving a valid request, the system MUST instantiate the case with all behavioral controls from the case type.
+
+**Feature tier**: MVP
+
+#### Scenario: Successful API intake
+
+- **GIVEN** a published case type "Omgevingsvergunning" with `processingDeadline = "P56D"` and initial status "Ontvangen"
+- **WHEN** an external system sends `POST /zaken/api/v1/zaken` with `zaaktype`, `omschrijving`, and `startdatum`
+- **THEN** the system MUST create the case in the `procest` register
+- **AND** `identifier` MUST be auto-generated (format: `YYYY-NNN`)
+- **AND** `deadline` MUST be calculated as `startdatum + P56D`
+- **AND** `status` MUST be set to the first status type by `order`
+- **AND** the system MUST return HTTP 201 with the case resource in ZGW format
+
+#### Scenario: Reject intake with invalid zaaktype
+
+- **GIVEN** a zaaktype URL that references a draft or expired case type
+- **WHEN** an external system sends a create request
+- **THEN** the system MUST return HTTP 400 with error: "Zaaktype is not published or not within its validity window"
+
+#### Scenario: API intake with all optional fields
+
+- **GIVEN** a valid create request including: zaaktype, omschrijving, startdatum, toelichting, vertrouwelijkheidaanduiding, zaakgeometrie
+- **WHEN** the system processes the request
+- **THEN** all provided fields MUST be mapped to the case properties (description, confidentiality, geometry)
+- **AND** fields not provided MUST use defaults from the case type
+
+#### Scenario: API authentication required
+
+- **GIVEN** a create request without valid authentication (JWT or Basic Auth)
+- **WHEN** the system receives the request
+- **THEN** the system MUST return HTTP 401 Unauthorized
+- **AND** no case MUST be created
+
+#### Scenario: API intake returns ZGW-compliant response
+
+- **GIVEN** a successful case creation via API
+- **WHEN** the system returns the response
+- **THEN** the response MUST include: `url` (self reference), `uuid`, `identificatie`, `omschrijving`, `zaaktype` (URL), `startdatum`, `status` (URL to first status), `einddatumGepland`
+- **AND** the response MUST conform to the ZGW Zaken API response schema
+
+---
+
+### Requirement: REQ-INTAKE-03 — Automatic Behandelaar Assignment
+
+The system SHALL support automatic assignment of a behandelaar based on case type configuration.
+
+**Feature tier**: MVP
+
+#### Scenario: Default handler from case type
+
+- **GIVEN** a case type "Subsidieaanvraag" with `defaultAssignee = "team-subsidies"` (a Nextcloud group)
+- **WHEN** a new case of this type is created via any channel
+- **THEN** the system MUST assign the case to the configured default assignee
+- **AND** a Nextcloud notification MUST be sent: "Nieuwe zaak toegewezen: [title]"
+
+#### Scenario: Round-robin assignment within team
+
+- **GIVEN** a case type with `assignmentStrategy = "round-robin"` and team members ["Jan", "Maria", "Pieter"]
+- **AND** Jan has 5 open cases, Maria has 3, Pieter has 4
+- **WHEN** a new case is created
+- **THEN** the system SHOULD assign to Maria (lowest workload)
+
+#### Scenario: No default assignee configured
+
+- **GIVEN** a case type with no `defaultAssignee` configured
+- **WHEN** a new case is created
+- **THEN** the case MUST be created without an assignee
+- **AND** the case MUST appear as "Unassigned" in the case list
+- **AND** the dashboard MUST count this case in the "unassigned" category
+
+#### Scenario: Assignment notification delivery
+
+- **GIVEN** a case assigned to handler "Jan de Vries"
+- **WHEN** the assignment is made
+- **THEN** the system MUST send a Nextcloud notification to Jan
+- **AND** the notification MUST include: case title, case type, and a link to the case detail
+- **AND** the notification MUST be visible in Jan's Nextcloud notification panel
+
+#### Scenario: Group assignment shows in case list
+
+- **GIVEN** a case assigned to group "team-subsidies" (3 members)
+- **WHEN** any member of team-subsidies views the case list
+- **THEN** the case MUST appear in their "My Work" view
+- **AND** the handler field MUST show "team-subsidies" until a specific person claims the case
+
+---
+
+### Requirement: REQ-INTAKE-04 — Initiator Role Creation
+
+The system MUST support linking an initiator (aanvrager) to a case during intake.
+
+**Feature tier**: MVP
+
+#### Scenario: Manual initiator assignment
+
+- **GIVEN** a case created via manual entry
+- **WHEN** the handler opens the participants panel and clicks "Add Participant"
+- **THEN** the handler MUST be able to select role "Aanvrager"
+- **AND** search for a person (BRP lookup) or enter a name manually
+- **AND** the selected person MUST be linked to the case with role "Aanvrager"
+
+#### Scenario: API intake with initiator BSN
+
+- **GIVEN** a ZGW API case creation request that includes a subsequent `POST /zaken/api/v1/rollen` with `betrokkeneType = "natuurlijk_persoon"` and BSN
+- **WHEN** the system processes the request
+- **THEN** the initiator MUST be created as a case participant with role "Aanvrager"
+- **AND** the BSN MUST be stored (encrypted per AVG requirements)
+- **AND** the initiator name MUST be resolved from BRP if available
+
+#### Scenario: API intake with initiator organization (KVK)
+
+- **GIVEN** a ZGW API role creation with `betrokkeneType = "niet_natuurlijk_persoon"` and KVK number
+- **WHEN** the system processes the request
+- **THEN** the organization MUST be linked as a case participant
+- **AND** the organization name MUST be resolved from KVK if available
+
+---
+
+### Requirement: REQ-INTAKE-05 — Initial Task Creation
+
+The system SHALL support automatic creation of initial tasks when a case is created, based on the case type configuration.
+
+**Feature tier**: V1
+
+#### Scenario: Auto-create intake checklist tasks
+
+- **GIVEN** a case type "Omgevingsvergunning" with initial tasks configured: ["Ontvangstbevestiging versturen", "Compleetheid toetsen", "Leges berekenen"]
+- **WHEN** a new case of this type is created
+- **THEN** the system MUST create 3 tasks linked to the case
+- **AND** each task MUST have status "available" and be assigned to the case handler
+- **AND** each task MUST have a due date relative to the case start date (if configured)
+
+#### Scenario: Task template with relative due date
+
+- **GIVEN** a task template "Ontvangstbevestiging versturen" with relativeDueDate = "P3D"
+- **WHEN** the task is auto-created for a case starting today
+- **THEN** the task dueDate MUST be set to today + 3 days
+
+#### Scenario: No initial tasks configured
+
+- **GIVEN** a case type "Melding openbare ruimte" with no initial task templates
+- **WHEN** a case is created
+- **THEN** no tasks MUST be auto-created
+- **AND** the tasks section in the case detail MUST show "No tasks"
+
+#### Scenario: Initial tasks assigned to handler
+
+- **GIVEN** a case type with initial tasks and defaultAssignee = "Jan de Vries"
+- **WHEN** a case is created and Jan is assigned as handler
+- **THEN** all auto-created tasks MUST be assigned to Jan
+- **AND** Jan MUST receive a notification for each task (or a single summary notification)
+
+---
+
+### Requirement: REQ-INTAKE-06 — Open Formulieren Integration
+
+The system MUST support receiving case submissions from Open Formulieren via ZGW API callback.
+
+**Feature tier**: V1
+
+#### Scenario: E-form submission creates case with attachments
+
+- **GIVEN** Open Formulieren configured with Procest as ZGW backend
+- **AND** a citizen submits form "Aanvraag omgevingsvergunning" with DigiD authentication
+- **WHEN** the form engine calls `POST /zaken/api/v1/zaken` followed by document uploads
+- **THEN** the system MUST create the case with the citizen as initiator (role type "Aanvrager")
+- **AND** uploaded documents MUST be linked to the case
+- **AND** BSN from DigiD MUST be stored on the initiator role (encrypted, AVG-compliant)
+- **AND** the system MUST send an ontvangstbevestiging notification
+
+#### Scenario: Form data mapped to custom properties
+
+- **GIVEN** a form that submits structured data (bouwkosten, oppervlakte, adres)
+- **WHEN** the case is created
+- **THEN** the system MUST map form fields to case property definitions where names match
+- **AND** unmapped fields MUST be stored as case metadata (not silently discarded)
+
+#### Scenario: Open Formulieren with file attachments
+
+- **GIVEN** a form submission with 3 PDF attachments (bouwtekening, constructieberekening, situatietekening)
+- **WHEN** the form engine uploads these via `POST /documenten/api/v1/enkelvoudiginformatieobjecten`
+- **THEN** each document MUST be stored in Nextcloud Files (IRootFolder)
+- **AND** each document MUST be linked to the case via `zaakinformatieobjecten`
+- **AND** the document type MUST be matched against the case type's document type configuration
+
+#### Scenario: DigiD BSN encrypted storage
+
+- **GIVEN** a citizen authenticates with DigiD (BSN 999993653)
+- **WHEN** the BSN is stored on the initiator role
+- **THEN** the BSN MUST be encrypted at rest
+- **AND** the BSN MUST only be accessible to users with the appropriate RBAC permission
+- **AND** access to BSN MUST be logged for AVG compliance
+
+---
+
+### Requirement: REQ-INTAKE-07 — Duplicate Detection
+
+The system SHALL detect potential duplicate submissions to prevent double case creation.
+
+**Feature tier**: V1
+
+#### Scenario: Warn on potential duplicate
+
+- **GIVEN** an existing case "Bouwvergunning Keizersgracht 100" for BSN 123456789
+- **WHEN** a new submission arrives for the same BSN with similar title within 24 hours
+- **THEN** the system MUST flag the intake as a potential duplicate
+- **AND** the behandelaar MUST be notified: "Mogelijke dubbele aanvraag gedetecteerd"
+- **AND** the case MUST still be created (not blocked) but marked for review
+
+#### Scenario: Duplicate detection criteria
+
+- **GIVEN** the duplicate detection system
+- **THEN** a potential duplicate MUST be flagged when ANY of the following match within 24 hours:
+ - Same BSN + same case type
+ - Same BAG address + same case type
+ - Title similarity > 80% (fuzzy match) + same case type
+- **AND** the handler MUST be able to dismiss the duplicate flag after review
+
+#### Scenario: Duplicate detection for API intake
+
+- **GIVEN** two API calls creating cases with the same zaaktype and similar omschrijving within 1 minute
+- **WHEN** the second case is created
+- **THEN** the system MUST flag it as a potential duplicate
+- **AND** the API response MUST include a header `X-Duplicate-Warning: possible` (but still return 201)
+
+#### Scenario: Dismiss duplicate flag
+
+- **GIVEN** a case flagged as potential duplicate
+- **WHEN** the handler reviews both cases and determines they are distinct
+- **THEN** the handler MUST be able to click "Not a duplicate" to dismiss the flag
+- **AND** the flag MUST be removed from the case
+- **AND** the audit trail MUST record: "Duplicate flag dismissed by [handler]"
+
+---
+
+### Requirement: REQ-INTAKE-08 — Intake Audit Trail
+
+The system MUST record the intake channel and source metadata in the case audit trail.
+
+**Feature tier**: MVP
+
+#### Scenario: Record intake source for manual entry
+
+- **GIVEN** a case created via the Procest UI
+- **WHEN** the case is stored
+- **THEN** the audit trail MUST record: intake channel "manual", created by user name, creation timestamp
+
+#### Scenario: Record intake source for API intake
+
+- **GIVEN** a case created via the ZGW API
+- **WHEN** the case is stored
+- **THEN** the audit trail MUST record: intake channel "zgw-api", authenticated client ID, source IP, creation timestamp
+
+#### Scenario: Record intake source for Open Formulieren
+
+- **GIVEN** a case created via Open Formulieren
+- **WHEN** the case is stored
+- **THEN** the audit trail MUST record: intake channel "open-formulieren", source form ID, submission timestamp, initiator BSN (hashed)
+- **AND** this information MUST be queryable for reporting (e.g., "how many cases came from e-forms this month")
+
+#### Scenario: Intake channel visible on case detail
+
+- **GIVEN** a case created via Open Formulieren
+- **WHEN** the handler views the case detail
+- **THEN** the case info panel MUST show the intake channel (e.g., "Bron: Open Formulieren")
+- **AND** clicking the source link SHOULD navigate to the original form submission (if available)
+
+---
+
+### Requirement: REQ-INTAKE-09 — E-mail Intake
+
+The system SHALL support creating cases from incoming e-mails via n8n workflow automation.
+
+**Feature tier**: V1
+
+#### Scenario: E-mail triggers case creation
+
+- **GIVEN** an n8n workflow configured to monitor an IMAP mailbox (e.g., info@gemeente.nl)
+- **AND** the workflow contains rules to categorize e-mails by subject keywords
+- **WHEN** an e-mail arrives with subject "Klacht over geluidsoverlast Keizersgracht"
+- **THEN** the workflow MUST create a case of type "Klacht behandeling" via the ZGW API
+- **AND** the e-mail body MUST be stored as the case description
+- **AND** e-mail attachments MUST be uploaded as case documents
+
+#### Scenario: E-mail sender as initiator
+
+- **GIVEN** an incoming e-mail from "petra.jansen@example.nl"
+- **WHEN** the case is created
+- **THEN** the sender MUST be linked as initiator (role "Aanvrager") with their e-mail address
+- **AND** if the e-mail matches a known contact (Pipelinq client), the system SHOULD auto-link
+
+#### Scenario: E-mail intake with unknown category
+
+- **GIVEN** an incoming e-mail that does not match any categorization rules
+- **WHEN** the n8n workflow processes it
+- **THEN** the workflow MUST create a case with a default case type (e.g., "Melding openbare ruimte")
+- **AND** the case MUST be flagged for manual categorization by the behandelaar
+
+---
+
+### Requirement: REQ-INTAKE-10 — Bulk Import
+
+The system SHALL support batch case creation via CSV or JSON file upload for migration or seasonal intake scenarios.
+
+**Feature tier**: V1
+
+#### Scenario: CSV bulk import
+
+- **GIVEN** an admin uploads a CSV file with columns: title, caseType, description, startDate, assignee
+- **AND** the CSV contains 50 rows
+- **WHEN** the admin initiates the import
+- **THEN** the system MUST validate all rows before creating any cases
+- **AND** validation errors MUST be reported per row (e.g., "Row 12: invalid case type 'Onbekend'")
+- **AND** the admin MUST confirm import after reviewing the validation report
+
+#### Scenario: Bulk import with validation errors
+
+- **GIVEN** a CSV with 50 rows, 3 of which have invalid case types
+- **WHEN** the admin views the validation report
+- **THEN** the system MUST show: "47 rows valid, 3 rows with errors"
+- **AND** the admin MUST choose: import valid rows only, fix errors and retry, or cancel
+
+#### Scenario: Bulk import progress
+
+- **GIVEN** 47 valid cases being imported
+- **WHEN** the import is in progress
+- **THEN** the system MUST show a progress indicator (e.g., "23/47 cases created...")
+- **AND** the system MUST handle failures gracefully (partial import is acceptable, failed rows are reported)
+
+---
+
+### Requirement: REQ-INTAKE-11 — Intake Channel Selection for Manual Entry
+
+The manual case creation form MUST allow recording the original intake channel even when the case is entered manually.
+
+**Feature tier**: MVP
+
+#### Scenario: Record intake channel on manual case
+
+- **GIVEN** a behandelaar creating a case from a phone call
+- **WHEN** the case creation form is displayed
+- **THEN** an optional "Intake Channel" dropdown MUST be available with options: "Balie" (counter), "Telefoon" (phone), "E-mail", "Post", "Website", "Overig" (other)
+- **AND** the selected channel MUST be stored on the case metadata
+
+#### Scenario: Default channel for manual entry
+
+- **GIVEN** a case created via the UI without selecting an intake channel
+- **WHEN** the case is stored
+- **THEN** the intake channel MUST default to "Manual" in the audit trail
+- **AND** the case info panel MUST show "Bron: Handmatig"
+
+#### Scenario: Intake channel reporting
+
+- **GIVEN** 50 cases created this month via various channels
+- **WHEN** an admin views intake channel statistics
+- **THEN** the system MUST be able to aggregate: X cases from Balie, Y from Telefoon, Z from E-mail, etc.
+- **AND** this data MUST be available via the OpenRegister API for reporting tools
+
+## Dependencies
+
+- **Case Management spec** (`../case-management/spec.md`): Intake creates cases; all case validation rules apply.
+- **Case Types spec** (`../case-types/spec.md`): Case type controls intake behavior (default assignee, initial tasks, required fields).
+- **Task Management spec** (`../task-management/spec.md`): Initial tasks are created per task spec.
+- **Roles & Decisions spec** (`../roles-decisions/spec.md`): Initiator role is created during intake.
+- **OpenRegister**: All case data stored as OpenRegister objects.
+- **OpenConnector**: ZGW API endpoint routing and StUF translation.
+- **n8n**: E-mail intake workflow automation.
+
+---
+
+### Using Mock Register Data
+
+This spec depends on the **BAG** and **DSO** mock registers for testing address validation and DSO intake (REQ-INTAKE-06, V1).
+
+**Loading the registers:**
+```bash
+# Load BAG register (32 addresses + 21 objects + 21 buildings, register slug: "bag")
+docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/bag_register.json
+
+# Load DSO register (53 records, register slug: "dso", schemas: "activiteit", "locatie", "omgevingsdocument", "vergunningaanvraag")
+docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/dso_register.json
+
+# Load BRP register for initiator BSN linking
+docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json
+```
+
+**Test data for this spec's use cases:**
+- **DSO intake (REQ-INTAKE-06)**: Use DSO `vergunningaanvraag` records to test omgevingsvergunning case creation with activiteiten, locatie, and bijlagen
+- **BAG address validation**: Use BAG `nummeraanduiding` records to test address resolution in form-to-case mapping
+- **Initiator with BSN**: BSN `999993653` (Suzanne Moulin) -- test initiator role creation with BSN from Open Formulieren DigiD flow
+- **DSO activiteiten**: Use DSO `activiteit` records (e.g., "Dakkapel plaatsen", "Aanbouw") for activity-to-zaaktype mapping
+
+### Current Implementation Status
+
+**Partially implemented (manual intake + ZGW API intake). V1 features not implemented.**
+
+**Implemented (with file paths):**
+- **Manual case creation (REQ-INTAKE-01)**: `src/views/cases/CaseCreateDialog.vue` provides a UI form for creating new cases. Users select a case type, fill in title, description, and other fields. The case is created via the object store against OpenRegister.
+- **ZGW API case creation (REQ-INTAKE-02)**: `lib/Controller/ZrcController.php` provides `POST /api/zgw/zaken/v1/zaken` endpoint for external systems to create cases via ZGW-compliant API. Supports zaaktype reference, omschrijving, startdatum, and other ZGW fields.
+- **ZGW business rules**: `lib/Service/ZgwBusinessRulesService.php` and `lib/Service/ZgwZrcRulesService.php` implement validation rules for case creation, including zaaktype validation, status initialization, and field mapping.
+- **ZGW mapping**: `lib/Service/ZgwMappingService.php` handles bidirectional mapping between ZGW Dutch terminology and Procest English field names.
+- **ZGW auth**: `lib/Middleware/ZgwAuthMiddleware.php` provides JWT-based authentication for ZGW API endpoints, allowing external systems to authenticate.
+- **Case type validation**: `src/utils/caseTypeValidation.js` provides client-side validation for case type data. `lib/Service/ZgwZtcRulesService.php` validates zaaktype status (draft/published, validity window).
+- **Deadline calculation**: The case type's `processingDeadline` (ISO 8601 duration) is used to calculate the case deadline. `src/utils/durationHelpers.js` supports ISO 8601 duration parsing. `src/views/cases/components/DeadlinePanel.vue` displays the calculated deadline.
+- **Status initialization**: New cases get their status set to the first status type (by order) of the case type. Implemented in ZGW business rules and CaseCreateDialog.
+- **Audit trail**: Case creation is logged via OpenRegister's audit trail. The `auditTrailsPlugin()` in the object store captures creation events (REQ-INTAKE-08).
+- **ZGW notification**: `lib/Controller/NrcController.php` and `lib/Service/NotificatieService.php` support ZGW notification webhooks for case lifecycle events.
+- **Identifier generation**: Case identifiers can be auto-generated (format depends on configuration).
+
+**Not yet implemented:**
+- **REQ-INTAKE-03: Automatic behandelaar assignment**: No default assignee configuration on case types. No round-robin assignment strategy. Cases are created without an assignee unless manually set.
+- **REQ-INTAKE-04: Initiator role creation**: No automatic creation of initiator role during intake from API. The role must be manually added via the Participants section (or via separate ZGW rollen API call).
+- **REQ-INTAKE-05: Initial task creation (V1)**: No automatic task creation based on case type configuration. No task templates on case types.
+- **REQ-INTAKE-06: Open Formulieren integration (V1)**: No integration with Open Formulieren. No DigiD/BSN handling. No form-to-case field mapping.
+- **REQ-INTAKE-07: Duplicate detection (V1)**: No duplicate submission detection.
+- **REQ-INTAKE-09: E-mail intake (V1)**: No IMAP trigger or e-mail-to-case conversion.
+- **REQ-INTAKE-10: Bulk import (V1)**: No CSV/JSON bulk case creation.
+- **REQ-INTAKE-11: Intake channel selection**: No intake channel dropdown on the manual creation form.
+- **Assignment notification**: No Nextcloud notification sent when a case is assigned to a handler.
+
+### Standards & References
+
+- **ZGW Zaken API**: `POST /zaken/api/v1/zaken` implemented via `ZrcController.php`. Supports the ZGW case creation flow with zaaktype validation, status initialization, and field mapping.
+- **StUF-ZKN**: `creeerZaak_Lk01` message format not implemented. StUF translation would require OpenConnector.
+- **CMMN 1.1**: Case instantiation follows CasePlanModel patterns with status lifecycle initialization.
+- **Common Ground**: Intake channels (API, forms, DSO) align with Common Ground information layer principles.
+- **Open Formulieren**: VNG's open-source form engine for citizen-facing e-forms. Integration via ZGW API callback.
+- **DSO (Digitaal Stelsel Omgevingswet)**: Environmental law digital system for permit applications.
+- **DigiD/eHerkenning**: Dutch government authentication for citizens (DigiD) and organizations (eHerkenning). BSN handling requires AVG-compliant encryption.
+- **AVG/GDPR**: BSN storage must be encrypted, access logged, and retention limited.
+- **Competitor reference**: Dimpact ZAC integrates with Open Formulieren and SmartDocuments for intake. CaseFabric provides multi-channel intake with auto-categorization. XXllnc Zaken supports DSO intake and StUF-ZKN for legacy system compatibility.
diff --git a/openspec/changes/zaak-intake-flow/tasks.md b/openspec/changes/archive/2026-05-11-zaak-intake-flow/tasks.md
similarity index 100%
rename from openspec/changes/zaak-intake-flow/tasks.md
rename to openspec/changes/archive/2026-05-11-zaak-intake-flow/tasks.md
diff --git a/openspec/changes/zaaktype-configuratie/.openspec.yaml b/openspec/changes/archive/2026-05-11-zaaktype-configuratie/.openspec.yaml
similarity index 100%
rename from openspec/changes/zaaktype-configuratie/.openspec.yaml
rename to openspec/changes/archive/2026-05-11-zaaktype-configuratie/.openspec.yaml
diff --git a/openspec/changes/zaaktype-configuratie/builds/build.json b/openspec/changes/archive/2026-05-11-zaaktype-configuratie/builds/build.json
similarity index 100%
rename from openspec/changes/zaaktype-configuratie/builds/build.json
rename to openspec/changes/archive/2026-05-11-zaaktype-configuratie/builds/build.json
diff --git a/openspec/changes/zaaktype-configuratie/design.md b/openspec/changes/archive/2026-05-11-zaaktype-configuratie/design.md
similarity index 100%
rename from openspec/changes/zaaktype-configuratie/design.md
rename to openspec/changes/archive/2026-05-11-zaaktype-configuratie/design.md
diff --git a/openspec/changes/zaaktype-configuratie/proposal.md b/openspec/changes/archive/2026-05-11-zaaktype-configuratie/proposal.md
similarity index 100%
rename from openspec/changes/zaaktype-configuratie/proposal.md
rename to openspec/changes/archive/2026-05-11-zaaktype-configuratie/proposal.md
diff --git a/openspec/changes/archive/2026-05-11-zaaktype-configuratie/specs/zaaktype-configuratie/spec.md b/openspec/changes/archive/2026-05-11-zaaktype-configuratie/specs/zaaktype-configuratie/spec.md
new file mode 100644
index 00000000..d7669701
--- /dev/null
+++ b/openspec/changes/archive/2026-05-11-zaaktype-configuratie/specs/zaaktype-configuratie/spec.md
@@ -0,0 +1,493 @@
+---
+status: implemented
+---
+# Zaaktype Configuratie Specification
+
+## Purpose
+
+Zaaktype Configuratie provides a zero-coding admin UI for configuring case types and all their behavioral components: status diagrams, checklists, required documents, deadlines, parafeerroutes, and property definitions. While the Case Types spec (`../case-types/spec.md`) defines the data model and validation rules, this spec covers the configuration UI and workflows that administrators use to set up and maintain case types without developer involvement.
+
+**Tender demand**: 23% of tenders (16/69) explicitly require zero-coding zaaktype configuration. Additionally, 36% of all tenders ask for "zero-coding configuratie" as a general principle. This is a key differentiator -- municipalities want to reduce leveranciersafhankelijkheid.
+**Relationship to existing specs**: This spec EXTENDS `case-types` (data model). It does NOT duplicate the data model or validation rules. It adds the admin UI and configuration workflows. Check `case-types` for all entity definitions.
+**Standards**: ZGW Catalogi API (ZaakType, StatusType, ResultaatType, InformatieObjectType), CMMN 1.1 (CaseDefinition)
+**Feature tier**: V1 (basic CRUD UI, status diagram editor, document type config, property definition config, role type config, result type config), V2 (visual flow designer, import/export, ZTC sync, versioning, test mode)
+
+## ADDED Requirements
+---
+
+### Requirement: REQ-ZTC-01 — Case Type CRUD via Admin UI
+
+The system MUST provide an admin interface for creating, editing, and managing case types without code changes.
+
+**Feature tier**: V1
+
+#### Scenario: Create new case type
+
+- **GIVEN** an admin navigating to Procest Admin > Zaaktypen
+- **WHEN** the admin clicks "Nieuw zaaktype"
+- **THEN** a form MUST be displayed with fields from the case-types spec: title, description, processingDeadline (ISO 8601 duration picker), confidentiality, validFrom, validUntil
+- **AND** the case type MUST be created in draft status
+- **AND** the admin MUST be warned: "Dit zaaktype is nog een concept. Publiceer het om zaken te kunnen aanmaken."
+
+#### Scenario: Edit existing case type
+
+- **GIVEN** a published case type "Omgevingsvergunning" with 10 active cases
+- **WHEN** the admin edits the case type
+- **THEN** the system MUST warn: "Er zijn 10 actieve zaken van dit type. Wijzigingen gelden alleen voor nieuwe zaken."
+- **AND** the admin MAY choose to create a new version instead of editing in-place
+
+#### Scenario: Publish a draft case type
+
+- **GIVEN** a draft case type "Bezwaarschrift" with at least one status type configured
+- **WHEN** the admin clicks "Publiceren"
+- **THEN** the system MUST validate that the case type has: at least one status type, at least one status marked as `isFinal`, a valid `processingDeadline`
+- **AND** if validation passes, the case type `isDraft` MUST be set to `false`
+- **AND** the case type MUST become available for case creation
+
+#### Scenario: Publish validation fails
+
+- **GIVEN** a draft case type "Nieuwe Procedure" with no status types configured
+- **WHEN** the admin clicks "Publiceren"
+- **THEN** the system MUST reject the publish with error: "Zaaktype kan niet gepubliceerd worden: geen statustypen geconfigureerd"
+- **AND** the case type MUST remain in draft status
+
+#### Scenario: Delete draft case type
+
+- **GIVEN** a draft case type "Test Zaaktype" with no active cases
+- **WHEN** the admin clicks "Verwijderen"
+- **THEN** the system MUST display a confirmation dialog
+- **AND** upon confirmation, the case type and all linked status types, document types, property definitions, and result types MUST be deleted
+
+#### Scenario: Delete published case type with active cases blocked
+
+- **GIVEN** a published case type "Omgevingsvergunning" with 10 active cases
+- **WHEN** the admin attempts to delete it
+- **THEN** the system MUST reject: "Kan niet verwijderd worden: er zijn 10 actieve zaken van dit type"
+- **AND** the case type MUST NOT be deleted
+
+#### Scenario: Set case type as default
+
+- **GIVEN** multiple published case types
+- **WHEN** the admin marks "Omgevingsvergunning" as the default
+- **THEN** the case creation form MUST pre-select this case type
+- **AND** only one case type MAY be the default at a time
+
+---
+
+### Requirement: REQ-ZTC-02 — Status Diagram Editor
+
+The system MUST provide a visual editor for configuring the status lifecycle of a case type.
+
+**Feature tier**: V1
+
+#### Scenario: Add and order statuses
+
+- **GIVEN** a new case type "Bezwaarschrift"
+- **WHEN** the admin opens the status configuration tab
+- **THEN** the admin MUST be able to add status types: "Ontvangen", "Vooronderzoek", "Hoorzitting", "Beslissing op bezwaar"
+- **AND** statuses MUST be orderable via drag-and-drop or order number input
+- **AND** the admin MUST mark "Beslissing op bezwaar" as `isFinal = true`
+- **AND** a visual diagram MUST show the status flow as a horizontal timeline
+
+#### Scenario: Configure status properties
+
+- **GIVEN** status type "Hoorzitting" on case type "Bezwaarschrift"
+- **WHEN** the admin edits the status
+- **THEN** the admin MUST be able to configure: description, `notifyInitiator` (yes/no), `notificationText`, required properties at this status, required documents at this status
+
+#### Scenario: Prevent deleting status with active cases
+
+- **GIVEN** a status type "In behandeling" that is the current status of 5 cases
+- **WHEN** the admin attempts to delete this status
+- **THEN** the system MUST reject: "Kan niet verwijderd worden: 5 zaken hebben deze status"
+
+#### Scenario: Reorder statuses
+
+- **GIVEN** a case type with statuses in order: "Ontvangen" (1), "In behandeling" (2), "Besluitvorming" (3), "Afgehandeld" (4)
+- **WHEN** the admin drags "Besluitvorming" before "In behandeling"
+- **THEN** the order MUST update to: "Ontvangen" (1), "Besluitvorming" (2), "In behandeling" (3), "Afgehandeld" (4)
+- **AND** the visual timeline MUST reflect the new order immediately
+
+#### Scenario: Status diagram color coding
+
+- **GIVEN** the visual status diagram
+- **THEN** each status type MUST display with a color indicator
+- **AND** the admin SHOULD be able to assign a color to each status (or use system defaults)
+- **AND** the colors MUST be used consistently in the case list and case detail views
+
+---
+
+### Requirement: REQ-ZTC-03 — Document Type Configuration
+
+The system MUST provide a UI for configuring which document types are required per case type.
+
+**Feature tier**: V1
+
+#### Scenario: Add required document types
+
+- **GIVEN** case type "Omgevingsvergunning"
+- **WHEN** the admin opens the document configuration tab
+- **THEN** the admin MUST be able to add document types with: name, direction (incoming/outgoing/internal), requiredAtStatus (dropdown of configured statuses), description
+- **AND** example: "Bouwtekening" (incoming, required at "In behandeling")
+
+#### Scenario: Edit document type
+
+- **GIVEN** a document type "Bouwtekening" configured as incoming, required at "In behandeling"
+- **WHEN** the admin changes requiredAtStatus to "Ontvangen"
+- **THEN** the system MUST update the document type configuration
+- **AND** the change MUST affect new cases only (existing cases retain their current checklist state)
+
+#### Scenario: Delete document type
+
+- **GIVEN** a document type "Welstandsadvies" on case type "Omgevingsvergunning"
+- **WHEN** the admin deletes it
+- **THEN** the document type MUST be removed from the case type configuration
+- **AND** existing cases MUST NOT lose already-uploaded documents of this type
+
+#### Scenario: Document type direction validation
+
+- **GIVEN** the admin adding a new document type
+- **THEN** the direction dropdown MUST only show: "incoming" (van aanvrager), "outgoing" (naar aanvrager), "internal" (intern)
+- **AND** each direction MUST have a localized label in Dutch and English
+
+---
+
+### Requirement: REQ-ZTC-04 — Property Definition Configuration
+
+The system MUST provide a UI for configuring custom property definitions (case-specific data fields) per case type.
+
+**Feature tier**: V1
+
+#### Scenario: Add custom properties
+
+- **GIVEN** case type "Omgevingsvergunning"
+- **WHEN** the admin opens the property definitions tab
+- **THEN** the admin MUST be able to add properties with: name, type (text/number/date/boolean/enum/reference), required (yes/no), requiredAtStatus, description, validation rules
+- **AND** example: "Bouwkosten" (number, required at "Ontvangen", min=0)
+
+#### Scenario: Enum property with predefined values
+
+- **GIVEN** a property "Type bouwwerk" on case type "Omgevingsvergunning"
+- **WHEN** the admin sets type to "enum"
+- **THEN** the admin MUST be able to define allowed values: ["Woning", "Bedrijfspand", "Bijgebouw", "Overig"]
+- **AND** the case form MUST render a dropdown with these options
+
+#### Scenario: Property with validation rules
+
+- **GIVEN** a property "Bouwkosten" of type "number"
+- **WHEN** the admin configures validation: min=0, max=10000000
+- **THEN** the case form MUST enforce these limits
+- **AND** the admin UI MUST display the configured validation rules clearly
+
+#### Scenario: Required-at-status property
+
+- **GIVEN** a property "Kadastraal nummer" with requiredAtStatus = "In behandeling"
+- **WHEN** the configuration is saved
+- **THEN** cases of this type MUST NOT be able to advance to "In behandeling" without this property filled
+- **AND** the case detail MUST visually indicate which properties are required at the next status
+
+#### Scenario: Reference property to external register
+
+- **GIVEN** a property "BAG adres" of type "reference"
+- **WHEN** the admin configures it to reference the BAG register's nummeraanduiding schema
+- **THEN** the case form MUST show a search field for looking up BAG addresses
+- **AND** the selected address MUST be stored as a reference to the BAG object
+
+---
+
+### Requirement: REQ-ZTC-05 — Role Type Configuration
+
+The system MUST provide a UI for configuring which role types are available per case type.
+
+**Feature tier**: V1
+
+#### Scenario: Add role types
+
+- **GIVEN** case type "Omgevingsvergunning"
+- **WHEN** the admin opens the role types tab
+- **THEN** the admin MUST be able to add role types with: name, description, maxCount (e.g., 1 for handler, unlimited for advisors)
+- **AND** example: "Behandelaar" (max 1), "Aanvrager" (max 1), "Technisch adviseur" (unlimited)
+
+#### Scenario: Role type with person/organization restriction
+
+- **GIVEN** a role type "Aanvrager"
+- **WHEN** the admin configures it
+- **THEN** the admin MUST be able to set whether the role can be filled by: person only, organization only, or both
+- **AND** this restriction MUST be enforced when adding participants to a case
+
+#### Scenario: Default role types pre-populated
+
+- **GIVEN** a new case type is created
+- **THEN** the system SHOULD pre-populate with default role types: "Behandelaar" and "Aanvrager"
+- **AND** the admin MAY add, edit, or remove these defaults
+
+---
+
+### Requirement: REQ-ZTC-06 — Result Type Configuration
+
+The system MUST provide a UI for configuring which result types are available per case type, including archival rules.
+
+**Feature tier**: V1
+
+#### Scenario: Add result types with archival rules
+
+- **GIVEN** case type "Omgevingsvergunning"
+- **WHEN** the admin opens the result types tab
+- **THEN** the admin MUST be able to add result types with: name, description, archiveAction (retain/destroy), retentionPeriod (ISO 8601 duration), retentionDateSource (case_completed/case_started)
+- **AND** example: "Vergunning verleend" (retain, P20Y, case_completed)
+
+#### Scenario: Result type selectielijst alignment
+
+- **GIVEN** the Dutch Selectielijst for archive management
+- **WHEN** the admin configures a result type
+- **THEN** the system SHOULD provide a selectielijst dropdown for common archival categories
+- **AND** the archiveAction and retentionPeriod SHOULD auto-fill based on the selectielijst selection
+
+#### Scenario: At least one result type required for publish
+
+- **GIVEN** a case type with no result types configured
+- **WHEN** the admin attempts to publish the case type
+- **THEN** the system MUST warn: "Geen resultaattypen geconfigureerd. Zaken kunnen niet worden afgesloten zonder resultaat."
+- **AND** the admin MAY proceed (result is optional at some case types) or add result types first
+
+---
+
+### Requirement: REQ-ZTC-07 — Parafeerroute Configuration
+
+The system MUST provide a UI for configuring B&W parafeerroutes per case type and voorstel type.
+
+**Feature tier**: V2
+
+#### Scenario: Configure parafeerroute
+
+- **GIVEN** case type "Omgevingsvergunning"
+- **WHEN** the admin opens the parafeerroute configuration
+- **THEN** the admin MUST be able to define ordered steps: step name, actor type (role/person/group), action (advise/parafeer/accord), parallel (yes/no)
+- **AND** the route MUST be previewable as a visual flow diagram
+
+#### Scenario: Parafeerroute with parallel steps
+
+- **GIVEN** a parafeerroute with 4 steps
+- **WHEN** the admin marks steps 2 and 3 as parallel
+- **THEN** the visual diagram MUST show steps 2 and 3 side by side
+- **AND** both MUST be completed before step 4 can start
+
+#### Scenario: Parafeerroute template reuse
+
+- **GIVEN** a parafeerroute configured on "Omgevingsvergunning"
+- **WHEN** the admin creates a new case type "Sloopvergunning"
+- **THEN** the admin SHOULD be able to copy the parafeerroute from "Omgevingsvergunning"
+- **AND** the copied route MUST be independently editable
+
+---
+
+### Requirement: REQ-ZTC-08 — Import and Export Configuration
+
+The system MUST support importing and exporting case type configurations for sharing between environments or municipalities.
+
+**Feature tier**: V2
+
+#### Scenario: Export case type as JSON
+
+- **GIVEN** a fully configured case type "Omgevingsvergunning" with 4 statuses, 5 document types, 8 properties, 4 role types, 3 result types, and a parafeerroute
+- **WHEN** the admin clicks "Exporteren"
+- **THEN** the system MUST generate a JSON file containing the complete configuration
+- **AND** the export MUST include all related entities (statuses, document types, properties, role types, result types, parafeerroute)
+- **AND** the export format MUST follow the OpenRegister JSON format with `@self` references
+
+#### Scenario: Import case type from JSON
+
+- **GIVEN** a JSON export from another Procest instance
+- **WHEN** the admin clicks "Importeren" and uploads the file
+- **THEN** the system MUST create the case type and all related entities in draft status
+- **AND** the admin MUST review and publish before the type becomes active
+- **AND** conflicts (e.g., duplicate names) MUST be flagged for resolution
+
+#### Scenario: ZTC catalog sync
+
+- **GIVEN** a ZGW Catalogi API endpoint with zaaktypen
+- **WHEN** the admin configures sync with the external catalog
+- **THEN** the system MUST import zaaktypen, statustypen, resultaattypen, and informatieobjecttypen
+- **AND** the imported types MUST be mapped to Procest's internal model
+
+#### Scenario: Export preserves relationships
+
+- **GIVEN** a case type with status type "In behandeling" referenced by a property definition (requiredAtStatus)
+- **WHEN** the case type is exported
+- **THEN** the export MUST preserve the relationship between property definition and status type
+- **AND** upon import, the relationship MUST be correctly re-established
+
+---
+
+### Requirement: REQ-ZTC-09 — Version Management
+
+The system SHALL support versioning of case type configurations.
+
+**Feature tier**: V2
+
+#### Scenario: Create new version
+
+- **GIVEN** a published case type "Omgevingsvergunning v1" with 50 active cases
+- **AND** a new regulation requires changes to the status flow
+- **WHEN** the admin clicks "Nieuwe versie aanmaken"
+- **THEN** the system MUST clone the current configuration as "Omgevingsvergunning v2" in draft
+- **AND** existing cases MUST remain linked to v1
+- **AND** new cases MUST use v2 once published
+- **AND** both versions MUST be visible in the admin overview
+
+#### Scenario: Version comparison
+
+- **GIVEN** two versions of "Omgevingsvergunning" (v1 and v2)
+- **WHEN** the admin views the version history
+- **THEN** the system SHOULD show a diff of changes between versions
+- **AND** the diff MUST highlight: added statuses, removed statuses, changed properties, changed deadlines
+
+#### Scenario: Retire old version
+
+- **GIVEN** "Omgevingsvergunning v1" with 3 remaining active cases (47 completed)
+- **WHEN** the admin retires v1
+- **THEN** the 3 active cases MUST remain on v1 until completion
+- **AND** no new cases can be created with v1
+- **AND** the admin overview MUST mark v1 as "retired"
+
+---
+
+### Requirement: REQ-ZTC-10 — Test Mode
+
+The system SHALL support testing a case type configuration before publishing.
+
+**Feature tier**: V2
+
+#### Scenario: Test case type in sandbox
+
+- **GIVEN** a draft case type "Nieuwe Subsidie"
+- **WHEN** the admin clicks "Testen"
+- **THEN** the system MUST allow creating a test case that does not appear in production views
+- **AND** the admin MUST be able to walk through the full lifecycle: status changes, document uploads, property filling
+- **AND** the test case MUST be automatically cleaned up after testing
+
+#### Scenario: Test mode visual indicator
+
+- **GIVEN** a test case created from a draft case type
+- **WHEN** the admin views the test case
+- **THEN** the case MUST display a prominent "TEST" banner
+- **AND** the case MUST NOT appear in dashboards, reports, or the main case list
+
+#### Scenario: Test mode limitations
+
+- **GIVEN** a test case
+- **THEN** the system MUST NOT send real notifications (initiator notifications, assignment notifications)
+- **AND** the test case MUST NOT be counted in KPIs or SLA metrics
+- **AND** the test case MUST be deletable without audit trail requirements
+
+---
+
+### Requirement: REQ-ZTC-11 — Admin Settings Navigation
+
+The admin UI MUST provide clear navigation between case type configuration areas.
+
+**Feature tier**: V1
+
+#### Scenario: Tab-based configuration
+
+- **GIVEN** an admin editing case type "Omgevingsvergunning"
+- **THEN** the configuration screen MUST show tabs: General, Statuses, Document Types, Properties, Role Types, Result Types
+- **AND** each tab MUST show the count of configured items (e.g., "Statuses (4)")
+- **AND** switching tabs MUST preserve unsaved changes or prompt to save
+
+#### Scenario: Case type list overview
+
+- **GIVEN** 5 case types: 3 published, 2 draft
+- **WHEN** the admin navigates to Procest Admin > Zaaktypen
+- **THEN** the list MUST show: title, status (published/draft badge), processing deadline, validity period, active case count, and actions (edit, delete, set default)
+- **AND** published case types MUST be visually distinct from drafts
+
+#### Scenario: Inline validation feedback
+
+- **GIVEN** the admin is configuring a case type
+- **WHEN** the admin leaves a required field empty (e.g., title)
+- **THEN** the system MUST show inline validation errors immediately
+- **AND** the "Save" button MUST be disabled while validation errors exist
+
+---
+
+### Requirement: REQ-ZTC-12 — Duration Picker for Processing Deadline
+
+The system MUST provide a user-friendly duration picker for the ISO 8601 processing deadline.
+
+**Feature tier**: V1
+
+#### Scenario: Duration picker input
+
+- **GIVEN** the admin is setting `processingDeadline` on a case type
+- **THEN** the system MUST provide a picker with fields for: weeks and/or days
+- **AND** the picker MUST convert the input to ISO 8601 duration format (e.g., 8 weeks = "P56D")
+- **AND** the picker MUST display common presets: "6 weken (P42D)", "8 weken (P56D)", "13 weken (P91D)", "26 weken (P182D)"
+
+#### Scenario: Custom duration entry
+
+- **GIVEN** the admin wants a non-standard deadline of 35 days
+- **WHEN** the admin enters "35 days" in the picker
+- **THEN** the system MUST store "P35D" as the processingDeadline
+- **AND** the display MUST show "35 dagen (5 weken)"
+
+#### Scenario: Duration picker for extension period
+
+- **GIVEN** a case type with `extensionAllowed = true`
+- **THEN** the admin MUST be able to set `extensionPeriod` using the same duration picker
+- **AND** common presets for extensions SHOULD be: "2 weken (P14D)", "4 weken (P28D)", "6 weken (P42D)"
+
+## Dependencies
+
+- **Case Types spec** (`../case-types/spec.md`): Defines the data model this UI configures.
+- **Case Management spec** (`../case-management/spec.md`): Cases use the configured case types.
+- **B&W Parafering spec** (`../bw-parafering/spec.md`): Parafeerroutes are configured per case type.
+- **Admin Settings spec** (`../admin-settings/spec.md`): Admin UI framework and navigation.
+- **OpenRegister**: All configuration stored as OpenRegister objects.
+
+---
+
+### Current Implementation Status
+
+**V1 partially implemented.** Basic case type CRUD and status configuration exist. Advanced features (document type config, property definition config, role type config, result type config, parafeerroute, import/export, versioning, test mode) are not implemented.
+
+**Implemented (with file paths):**
+- **Case type CRUD via admin UI (REQ-ZTC-01)**:
+ - `src/views/settings/CaseTypeList.vue` -- lists all case types with title, draft/published badge, processing deadline, validity period, and actions (set default, edit, delete).
+ - `src/views/settings/CaseTypeDetail.vue` -- detail/edit view for a single case type with tabs.
+ - `src/views/settings/CaseTypeAdmin.vue` -- admin wrapper component.
+ - `src/views/settings/AdminRoot.vue` -- admin root with case type list and detail.
+ - `src/views/settings/tabs/GeneralTab.vue` -- general properties tab for case type editing (title, description, processingDeadline, confidentiality, etc.).
+- **Status diagram editor (REQ-ZTC-02 partial)**:
+ - `src/views/settings/tabs/StatusesTab.vue` -- status type configuration within a case type. Supports adding, ordering, and editing statuses. Includes `isFinal` marking.
+ - `src/views/cases/components/StatusTimeline.vue` -- visual timeline showing status progression on case detail.
+- **Case type validation**: `src/utils/caseTypeValidation.js` -- client-side validation for case type fields.
+- **Navigation**: `src/navigation/MainMenu.vue` -- "Case Types" menu item in the settings footer, linked to `/case-types` route.
+- **Router**: `src/router/index.js` -- route `{ path: '/case-types', name: 'CaseTypes', component: AdminRoot }`.
+- **Schema definitions**: All 7 configuration schemas defined in `lib/Settings/procest_register.json`: `caseType`, `statusType`, `resultType`, `roleType`, `propertyDefinition`, `documentType`, `decisionType`.
+- **ZGW catalog API**: `lib/Controller/ZtcController.php` -- full ZGW Catalogi API with CRUD for zaaktypen, statustypen, resultaattypen, informatieobjecttypen. Includes publish endpoints (`POST .../zaaktypen/{uuid}/publish`).
+- **ZGW catalog rules**: `lib/Service/ZgwZtcRulesService.php` -- validation rules for zaaktype creation and modification.
+
+**Not yet implemented:**
+- **REQ-ZTC-03: Document type configuration (V1)**: No admin UI for configuring document types per case type. The `documentType` schema exists but no management UI.
+- **REQ-ZTC-04: Property definition configuration (V1)**: No admin UI for configuring custom properties per case type. The `propertyDefinition` schema exists but no management UI. No enum value editor.
+- **REQ-ZTC-05: Role type configuration (V1)**: No admin UI for configuring role types per case type. The `roleType` schema exists but no management UI.
+- **REQ-ZTC-06: Result type configuration (V1)**: No admin UI for configuring result types per case type. The `resultType` schema exists but no management UI.
+- **REQ-ZTC-07: Parafeerroute configuration (V2)**: No parafeerroute configuration UI. No visual flow diagram for approval routes.
+- **REQ-ZTC-08: Import/export configuration (V2)**: No JSON export/import for case type configurations. No ZTC catalog sync.
+- **REQ-ZTC-09: Version management (V2)**: No versioning of case type configurations. No clone/new version functionality.
+- **REQ-ZTC-10: Test mode (V2)**: No sandbox/test mode for case types.
+- **REQ-ZTC-12: Duration picker (V1)**: No duration picker component; processingDeadline is entered as raw ISO 8601 string.
+- **Status drag-and-drop ordering**: Status ordering may be manual (number input) rather than drag-and-drop.
+- **Warning on editing published case type**: No "10 active cases" warning when editing a published case type.
+- **Publish validation**: No pre-publish validation checking for status types, final status, and processing deadline.
+
+### Standards & References
+
+- **ZGW Catalogi API (VNG Realisatie)**: Full ZGW-compliant catalog API via `ZtcController.php`. Supports ZaakType, StatusType, ResultaatType, RolType, InformatieObjectType, BesluitType, Eigenschap. Includes publish endpoints.
+- **CMMN 1.1**: CaseDefinition patterns for case type configuration with status lifecycle.
+- **GEMMA**: Zaaktype catalogus is a standard component in the GEMMA reference architecture.
+- **Common Ground**: Configuration data stored as OpenRegister objects in the information layer.
+- **Selectielijst**: Dutch archival selection list determining retention periods per case type category.
+- **Archiefwet**: Archival rules linked to result types (retain/destroy with retention periods).
+- **Competitor reference**: Dimpact ZAC provides a zaaktype admin interface integrated with Flowable CMMN modeling. CaseFabric offers a visual case type designer with drag-and-drop status flows. Flowable Design provides low-code case definition with visual CMMN editor.
diff --git a/openspec/changes/zaaktype-configuratie/tasks.md b/openspec/changes/archive/2026-05-11-zaaktype-configuratie/tasks.md
similarity index 100%
rename from openspec/changes/zaaktype-configuratie/tasks.md
rename to openspec/changes/archive/2026-05-11-zaaktype-configuratie/tasks.md
diff --git a/openspec/changes/zgw-api-mapping/builds/build.json b/openspec/changes/archive/2026-05-11-zgw-api-mapping/builds/build.json
similarity index 100%
rename from openspec/changes/zgw-api-mapping/builds/build.json
rename to openspec/changes/archive/2026-05-11-zgw-api-mapping/builds/build.json
diff --git a/openspec/changes/zgw-api-mapping/design.md b/openspec/changes/archive/2026-05-11-zgw-api-mapping/design.md
similarity index 100%
rename from openspec/changes/zgw-api-mapping/design.md
rename to openspec/changes/archive/2026-05-11-zgw-api-mapping/design.md
diff --git a/openspec/changes/zgw-api-mapping/proposal.md b/openspec/changes/archive/2026-05-11-zgw-api-mapping/proposal.md
similarity index 100%
rename from openspec/changes/zgw-api-mapping/proposal.md
rename to openspec/changes/archive/2026-05-11-zgw-api-mapping/proposal.md
diff --git a/openspec/changes/zgw-api-mapping/specs/zgw-api-mapping/spec.md b/openspec/changes/archive/2026-05-11-zgw-api-mapping/specs/zgw-api-mapping/spec.md
similarity index 97%
rename from openspec/changes/zgw-api-mapping/specs/zgw-api-mapping/spec.md
rename to openspec/changes/archive/2026-05-11-zgw-api-mapping/specs/zgw-api-mapping/spec.md
index 14c55d30..784e65a7 100644
--- a/openspec/changes/zgw-api-mapping/specs/zgw-api-mapping/spec.md
+++ b/openspec/changes/archive/2026-05-11-zgw-api-mapping/specs/zgw-api-mapping/spec.md
@@ -5,8 +5,7 @@ the existing `zgw-api-mapping` canonical spec. REQ-ZGW-1..10 below
complement (not replace) the canonical requirements, which define HOW
mapping works; these requirements define WHAT procest maps.
-## Requirements
-
+## ADDED Requirements
### Requirement: REQ-ZGW-1 — Procest MUST declare a ZGW mapping profile for every ZGW resource it exposes
Procest MUST ship a `ZgwMappingProfile` per ZGW resource, declaring the
@@ -96,6 +95,8 @@ including required `bronorganisatie`, `verantwoordelijkeOrganisatie`,
### Requirement: REQ-ZGW-4 — Procest MUST map case sub-resources (Status, Resultaat, Rol, ZaakEigenschap)
+The system SHALL satisfy the behaviour described as "REQ-ZGW-4 — Procest MUST map case sub-resources (Status, Resultaat, Rol, ZaakEigenschap)".
+
The procest sub-resources (`statusRecord`, `result`, `role`, `caseProperty`)
MUST translate to their ZRC counterparts (Status, Resultaat, Rol,
ZaakEigenschap), with the parent zaak referenced by URL.
@@ -117,6 +118,8 @@ ZaakEigenschap), with the parent zaak referenced by URL.
### Requirement: REQ-ZGW-5 — Procest MUST map case type schemas to ZTC resources
+The system SHALL satisfy the behaviour described as "REQ-ZGW-5 — Procest MUST map case type schemas to ZTC resources".
+
The procest `caseType`, `statusType`, `resultType`, `roleType`,
`propertyDefinition`, `documentType`, `decisionType`, and `catalogus`
schemas MUST translate to their ZTC counterparts. Inbound mapping is
diff --git a/openspec/changes/zgw-api-mapping/tasks.md b/openspec/changes/archive/2026-05-11-zgw-api-mapping/tasks.md
similarity index 100%
rename from openspec/changes/zgw-api-mapping/tasks.md
rename to openspec/changes/archive/2026-05-11-zgw-api-mapping/tasks.md
diff --git a/openspec/changes/bw-parafering/specs/bw-parafering/spec.md b/openspec/changes/bw-parafering/specs/bw-parafering/spec.md
deleted file mode 100644
index e952e233..00000000
--- a/openspec/changes/bw-parafering/specs/bw-parafering/spec.md
+++ /dev/null
@@ -1,526 +0,0 @@
----
-status: implemented
----
-# B&W Parafering & Besluitvorming Specification
-
-## Purpose
-
-B&W parafering covers the ambtelijk (civil servant) workflow for preparing, reviewing, and approving proposals before they reach the College van B&W for formal decision-making. The bestuurlijk (political) part -- agenda management, vergadering, and besluitenlijst -- is handled by external RIS systems (iBabs, NotuBiz). This spec covers the ambtelijk workflow and the connector to the RIS.
-
-**Tender demand**: Found in 20+ tenders (29% of all, higher among generic zaaksysteem tenders). B&W besluitvorming is the #6 Nice-to-have but weighs heavily in scoring (typically 3-8% of total score, up to 68 points).
-**Standards**: BPMN 2.0 (process modeling), ZGW Besluiten API, ZDS (Zaak-Document Services for legacy RIS), CMMN 1.1 (HumanTask for parafering steps)
-**Feature tier**: V1 (ambtelijk parafering, sequential routing, audit trail), V2 (parallel parafering, mobile parafering, iBabs/NotuBiz connector, vergaderbeheer)
-
-**Competitive context**: Dimpact ZAC implements decision management via the ZGW BRC API with besluittype validation, publication date handling, and document linking. ZAC does NOT include B&W parafering workflow -- that is handled externally. Flowable's CMMN engine can model parafeerroutes as sequential/parallel HumanTasks with configurable completion rules. ArkCase and CaseFabric both provide full approval workflows with configurable routing. Procest should implement parafering as OpenRegister objects with task-based routing, leveraging the existing task management infrastructure.
-
-## Standard Workflow (10-Step Process)
-
-Reconstructed from 20+ tender analyses, this is the standard B&W besluitvormingsproces:
-
-| Step | Actor | Action | System |
-|------|-------|--------|--------|
-| 1 | Steller | Creates advies/voorstel from case context | Procest |
-| 2 | Adviseur(s) | Provide internal advice on the voorstel | Procest |
-| 3 | Parafeerder(s) | Paraferen the voorstel (sequential or parallel) | Procest |
-| 4 | Manager/Afdelingshoofd | Accordeert the voorstel | Procest |
-| 5 | Portefeuillehouder (wethouder) | Accordeert the voorstel | Procest |
-| 6 | Secretariaat/Agendacommissie | Reviews quality, places on agenda | Procest + RIS |
-| 7 | BMO/Kwaliteitstoets | Technical and legal quality check | Procest |
-| 8 | College B&W | Treats voorstel (hamerstuk or bespreekstuk) | RIS (iBabs/NotuBiz) |
-| 9 | Besluitenlijst | Decision recorded and published | RIS -> Procest |
-| 10 | Archivering | Besluit linked back to case and archived | Procest |
-
-**Key principle**: Steps 1-7 are ambtelijk (in Procest). Steps 8-9 are bestuurlijk (in the RIS). Step 10 bridges back.
-
-### OpenRegister Schema Model
-
-```
-voorstel:
- case: reference # -> case
- type: enum # dt_advies | collegeadvies | raadsvoorstel
- onderwerp: string # from case title
- steller: string # user UID who created
- afdeling: string # department
- portefeuillehouder: string # wethouder UID
- status: enum # concept | in_parafering | ter_accordering | geaccordeerd | aangeboden | besloten | gearchiveerd | teruggestuurd
- parafeerroute: reference # -> parafeerroute
- currentStep: integer # current step number in route
- document: string # Nextcloud file ID for the voorstel document
- bijlagen: array # Nextcloud file IDs for attachments
- behandeling: enum # hamerstuk | bespreekstuk
- createdAt: datetime
- updatedAt: datetime
-
-parafeerroute:
- name: string # "Collegeadvies - Omgevingsvergunning"
- caseType: reference # -> caseType (optional, for default route)
- voorstelType: enum # dt_advies | collegeadvies | raadsvoorstel
- steps: array # ordered list of parafeerstap
-
-parafeerstap:
- order: integer # 1, 2, 3...
- type: enum # advies | parafering | accordering
- actor: string # user UID or role name
- actorType: enum # user | group | role
- parallel: boolean # if true, all actors in this step must complete
- parallelActors: array # list of user UIDs for parallel parafering
- completionRule: enum # all | any (for parallel: all must complete, or any one)
- mandatory: boolean # if false, step can be skipped
-
-parafeeractie:
- voorstel: reference # -> voorstel
- step: integer # step number
- actor: string # user UID who performed action
- actorType: enum # user | delegate
- onBehalfOf: string # user UID if acting on behalf of someone
- action: enum # parafered | returned | advised | skipped
- comment: string # optional comment
- advice: string # for advisory steps
- timestamp: datetime
- mandate: string # mandate reference if acting on behalf
-```
-
-## Requirements
-
----
-
-### REQ-BW-01: Voorstel Creation from Case
-
-The system MUST support creating a B&W-voorstel (college proposal) from within a case context.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-01a: Create college voorstel
-
-- GIVEN a case "Bestemmingsplan Centrum" at status "Besluitvorming"
-- WHEN the steller clicks "Nieuw B&W-voorstel" in the case dashboard
-- THEN the system MUST create a voorstel object linked to the case in OpenRegister
-- AND the voorstel MUST include: onderwerp (from case title), steller (current user), afdeling (from case type config), portefeuillehouder (from case type config)
-- AND a document template "Collegeadvies" MUST be generated via Docudesk with case data pre-filled
-- AND the case documents MUST be available as bijlagen to the voorstel
-
-#### Scenario BW-01b: Voorstel types
-
-- GIVEN voorstel types: "DT-advies" (directieteam), "Collegeadvies", "Raadsvoorstel"
-- WHEN the steller creates a new voorstel
-- THEN the steller MUST select the voorstel type from a dropdown
-- AND the parafeerroute MUST be loaded from the case type configuration for that voorstel type
-- AND the selected type MUST determine which document template is used
-
-#### Scenario BW-01c: Voorstel from case dashboard panel
-
-- GIVEN a case dashboard with a "B&W Voorstellen" panel
-- WHEN the panel is empty (no voorstellen yet)
-- THEN the panel MUST show: "Geen voorstellen" with a "Nieuw voorstel" button
-- AND when a voorstel exists, it MUST show: type, status, current parafeeerstap, steller
-
-#### Scenario BW-01d: Multiple voorstellen per case
-
-- GIVEN a case with an existing "DT-advies" voorstel (status: besloten)
-- WHEN the steller creates a new "Collegeadvies" voorstel
-- THEN both voorstellen MUST be visible in the case dashboard panel
-- AND the history of the DT-advies MUST remain accessible
-
-#### Scenario BW-01e: Pre-fill voorstel from case data
-
-- GIVEN a case with properties: bouwkosten, locatie, aanvrager
-- WHEN creating a collegeadvies voorstel
-- THEN the Docudesk template MUST pre-fill: onderwerp, zaaknummer, locatie, aanvrager, bouwkosten
-- AND the steller MUST be able to edit the generated document before submitting for parafering
-
----
-
-### REQ-BW-02: Configurable Parafeerroute
-
-The system MUST support configurable parafeerroutes per case type and voorstel type. The route defines who must paraferen/accorderen and in what order.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-02a: Sequential parafering
-
-- GIVEN a parafeerroute for "Collegeadvies" on case type "Omgevingsvergunning":
- 1. Adviseur vakinhoud (advisory)
- 2. Juridisch adviseur (advisory)
- 3. Teamleider (parafering)
- 4. Afdelingshoofd (parafering)
- 5. Portefeuillehouder (accordering)
-- WHEN the steller submits the voorstel for parafering
-- THEN the system MUST route to step 1 first
-- AND each step MUST complete before the next step is activated
-- AND each actor MUST receive a Nextcloud notification and a task in their "Mijn taken" list
-
-#### Scenario BW-02b: Parallel parafering
-
-- GIVEN a parafeerroute with step 3 configured as parallel: [Teamleider A, Teamleider B]
-- AND completionRule = "all"
-- WHEN step 3 is reached
-- THEN both Teamleider A and Teamleider B MUST receive the voorstel simultaneously
-- AND the step completes when ALL parallel actors have parafered
-- AND the voorstel status MUST show "Wacht op 2 parafen" until both complete
-
-#### Scenario BW-02c: Override parafeerroute
-
-- GIVEN the standard route requires 5 steps
-- AND an authorized manager wants to skip the vakinhoudelijk advies step
-- WHEN the manager removes step 1 from the route for this specific voorstel
-- THEN the system MUST allow the modification
-- AND the audit trail MUST record: "Parafeerroute aangepast: stap 'Adviseur vakinhoud' overgeslagen door [manager], reden: [text]"
-- AND a reason MUST be mandatory when skipping steps
-
-#### Scenario BW-02d: Add ad-hoc step
-
-- GIVEN a voorstel at step 2 of 5
-- WHEN the steller or manager adds an ad-hoc advisory step "Financieel adviseur" between step 2 and 3
-- THEN the route MUST be adjusted: steps 3-5 become 4-6, new step 3 is the ad-hoc step
-- AND the audit trail MUST record: "Stap toegevoegd: 'Financieel adviseur' door [user]"
-
-#### Scenario BW-02e: Admin route configuration
-
-- GIVEN the beheerder opens Procest admin settings
-- WHEN navigating to "Parafeerroutes" configuration
-- THEN the beheerder MUST be able to:
- - Create a new route with named steps
- - Assign each step a type (advies/parafering/accordering), actor type (user/group/role), and parallel flag
- - Link the route to a case type and voorstel type
- - Set a route as the default for a case type
-
----
-
-### REQ-BW-03: Parafering Actions
-
-Each actor in the parafeerroute MUST be able to perform specific actions on the voorstel.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-03a: Paraferen (approve)
-
-- GIVEN a voorstel at step "Teamleider" assigned to "Jan de Vries"
-- WHEN Jan clicks "Paraferen" in his task or in the voorstel detail view
-- THEN the system MUST record a parafeeractie: actor=Jan, action=parafered, timestamp=now
-- AND the voorstel MUST advance to the next step
-- AND Jan MUST NOT be able to paraferen again on this voorstel
-- AND a notification MUST be sent to the next actor in the route
-
-#### Scenario BW-03b: Return with comments (terugsturen)
-
-- GIVEN a voorstel at step "Afdelingshoofd"
-- WHEN the afdelingshoofd clicks "Terugsturen" with comment "Financiele paragraaf ontbreekt"
-- THEN the voorstel MUST be returned to the steller (status: teruggestuurd)
-- AND the comment MUST be visible to the steller in the voorstel detail
-- AND the audit trail MUST record the return with reason
-- AND the steller MUST be notified: "Voorstel teruggestuurd door [afdelingshoofd]: Financiele paragraaf ontbreekt"
-- AND the steller MUST be able to edit the document and resubmit (resumes from the returning step)
-
-#### Scenario BW-03c: Adviseren (non-binding opinion)
-
-- GIVEN a voorstel at an advisory step (not parafering)
-- WHEN the adviseur submits advice: "Akkoord, mits bouwkosten worden gecontroleerd"
-- THEN the advice MUST be attached to the voorstel as a parafeeractie with action=advised
-- AND the voorstel MUST advance to the next step (advice is non-blocking)
-- AND the steller and subsequent parafeerders MUST be able to see the advice in the voorstel detail
-
-#### Scenario BW-03d: Paraferen namens (on behalf of)
-
-- GIVEN portefeuillehouder wethouder Van Dam is unavailable
-- AND secretaresse Bakker has mandate to paraferen namens Van Dam (configured in admin settings)
-- WHEN Bakker opens the voorstel task
-- THEN Bakker MUST see an option "Paraferen namens Van Dam"
-- AND the audit trail MUST record: "Geparafeerd door Bakker namens Van Dam (mandaat: [reference])"
-
-#### Scenario BW-03e: View voorstel document during parafering
-
-- GIVEN a parafeerder receives a voorstel task
-- WHEN opening the voorstel detail view
-- THEN the voorstel document MUST be viewable inline (PDF preview or document viewer)
-- AND all bijlagen MUST be listed and downloadable
-- AND previous advice from earlier steps MUST be visible
-- AND the parafering history MUST show which steps are completed
-
----
-
-### REQ-BW-04: Mobile Parafering
-
-The system MUST support parafering from mobile devices (tablets, smartphones) for bestuurders who are frequently on the move.
-
-**Feature tier**: V2
-
-
-#### Scenario BW-04a: Paraferen on tablet
-
-- GIVEN wethouder Van Dam viewing pending voorstellen on a tablet
-- WHEN Van Dam opens voorstel "Bestemmingsplan Centrum"
-- THEN the voorstel document and bijlagen MUST be readable on the tablet
-- AND "Paraferen" and "Terugsturen" buttons MUST be accessible
-- AND the UI MUST be responsive (no pinch-to-zoom required for core actions)
-- AND touch targets MUST be at least 44x44px per WCAG AA
-
-#### Scenario BW-04b: Offline document access
-
-- GIVEN a wethouder preparing for a vergadering without reliable internet
-- WHEN the wethouder opens the Nextcloud mobile app
-- THEN voorstel documents that were previously viewed MUST be available offline (Nextcloud Files offline sync)
-- AND parafering actions MUST queue and sync when connectivity returns
-
-#### Scenario BW-04c: Push notification for pending parafering
-
-- GIVEN a new voorstel awaiting Van Dam's parafering
-- WHEN the voorstel reaches Van Dam's step
-- THEN a push notification MUST be sent via the Nextcloud mobile app: "Nieuw voorstel ter parafering: [onderwerp]"
-- AND tapping the notification MUST open the voorstel detail
-
----
-
-### REQ-BW-05: RIS Connector (iBabs/NotuBiz)
-
-The system MUST support pushing approved voorstellen to the external RIS for bestuurlijke behandeling, and receiving besluiten back.
-
-**Feature tier**: V2
-
-
-#### Scenario BW-05a: Push voorstel to iBabs
-
-- GIVEN a voorstel that has completed all ambtelijke parafering steps (status: geaccordeerd)
-- AND the secretariaat marks it for agendering with behandeling type (hamerstuk/bespreekstuk)
-- WHEN the secretariaat clicks "Aanbieden aan iBabs"
-- THEN the system MUST push via iBabs API: voorstel document, bijlagen, metadata (onderwerp, portefeuillehouder, hamerstuk/bespreekstuk)
-- AND the voorstel status MUST change to "Aangeboden aan college"
-- AND the push status MUST be tracked: "Verstuurd", "Ontvangen", "Fout"
-
-#### Scenario BW-05b: Receive besluit from iBabs
-
-- GIVEN a voorstel treated in the college vergadering
-- AND the besluit is recorded in iBabs
-- WHEN the besluit is synced back to Procest (via API polling or webhook through OpenConnector)
-- THEN the system MUST create a Besluit object linked to the case via the BRC controller
-- AND the case timeline MUST show: "College besluit: [besluit tekst]"
-- AND the voorstel status MUST change to "Besloten"
-- AND the besluit document from iBabs MUST be stored in Nextcloud Files linked to the case
-
-#### Scenario BW-05c: NotuBiz connector
-
-- GIVEN a municipality using NotuBiz instead of iBabs
-- WHEN the connector is configured for NotuBiz in OpenConnector
-- THEN the same push/receive flow MUST work via NotuBiz API or ZIP(XML+PDF) exchange
-- AND the system MUST support both iBabs and NotuBiz as pluggable RIS adapters
-
-#### Scenario BW-05d: RIS connector not configured
-
-- GIVEN no RIS connector is configured
-- WHEN the secretariaat views the voorstel
-- THEN the "Aanbieden aan RIS" button MUST be hidden
-- AND a manual "Markeer als besloten" button MUST allow recording the besluit without a RIS
-
----
-
-### REQ-BW-06: Parafering Audit Trail
-
-The system MUST maintain an immutable audit trail of all parafering actions. This is a legal requirement -- the trail must be reconstructable for accountability and Archiefwet compliance.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-06a: Complete audit trail
-
-- GIVEN a voorstel that has passed through 5 parafering steps
-- WHEN an auditor reviews the voorstel
-- THEN the audit trail MUST show for each step: step number, step type (advies/parafering/accordering), actor, action (parafered/returned/advised/skipped), timestamp, comments
-- AND no entries MAY be deleted or modified after recording (immutable)
-- AND the trail MUST be exportable as PDF for archival
-
-#### Scenario BW-06b: Route modification audit
-
-- GIVEN a parafeerroute was modified (step skipped or added)
-- THEN the audit trail MUST include route modification events: who modified, what changed, reason provided
-- AND the original route definition MUST be preserved alongside the modified version
-
-#### Scenario BW-06c: Delegation audit
-
-- GIVEN parafering was performed by a delegate (namens)
-- THEN the audit trail MUST clearly distinguish: "Geparafeerd door [delegate] namens [principal] op basis van mandaat [reference]"
-- AND both the delegate and principal MUST be searchable in audit queries
-
----
-
-### REQ-BW-07: Parafering Dashboard
-
-The system MUST provide an overview of all active voorstellen and their parafering status.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-07a: Secretariaat overview
-
-- GIVEN 8 active voorstellen in various stages of parafering
-- WHEN the secretariaat views the parafering dashboard
-- THEN each voorstel MUST show: onderwerp, current step, waiting actor, days in current step, overall progress (step 3/5)
-- AND voorstellen overdue on any step (waiting > configured threshold) MUST be highlighted in orange/red
-- AND the secretariaat MUST be able to send reminders to actors who have not yet parafered
-
-#### Scenario BW-07b: Personal parafering inbox
-
-- GIVEN wethouder Van Dam has 3 voorstellen awaiting his parafering
-- WHEN Van Dam opens his parafering inbox (in My Work or as separate view)
-- THEN the 3 voorstellen MUST be listed with: onderwerp, case reference, steller, waiting since
-- AND each item MUST be actionable directly (paraferen/terugsturen without opening full detail)
-
-#### Scenario BW-07c: Pipeline visualization
-
-- GIVEN 12 voorstellen in the parafering pipeline
-- WHEN the secretariaat views the pipeline
-- THEN a Kanban-style board MUST show columns per parafering phase: Concept, In parafering, Ter accordering, Geaccordeerd, Aangeboden aan college, Besloten
-- AND each voorstel MUST be a card showing: onderwerp, steller, days in phase
-
-#### Scenario BW-07d: Send reminder
-
-- GIVEN a voorstel has been waiting at step "Afdelingshoofd" for 5 days (threshold: 3 days)
-- WHEN the secretariaat clicks "Herinnering sturen"
-- THEN a Nextcloud notification MUST be sent to the afdelingshoofd: "Voorstel '[onderwerp]' wacht op uw parafering (5 dagen)"
-- AND the reminder MUST be logged in the audit trail
-
----
-
-### REQ-BW-08: Voorstel Detail View
-
-The system MUST provide a dedicated detail view for voorstellen, showing the document, parafering progress, and actions.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-08a: View voorstel detail
-
-- GIVEN a voorstel "Collegeadvies Bestemmingsplan Centrum"
-- WHEN any authorized user opens the voorstel detail
-- THEN the view MUST show:
- - Header: onderwerp, type, steller, afdeling, status
- - Document viewer: inline preview of the voorstel document
- - Bijlagen: list of attached documents
- - Parafering progress: visual step indicator showing completed/current/future steps
- - Action history: all parafeeracties with timestamps, actors, comments
- - Case reference: link back to the parent case
-
-#### Scenario BW-08b: Action buttons per role
-
-- GIVEN the current user is the active parafeerder at the current step
-- THEN the voorstel detail MUST show action buttons: "Paraferen", "Terugsturen"
-- AND if the step type is "advies", the button MUST be "Adviseren" instead of "Paraferen"
-- AND if the user is NOT the active actor, action buttons MUST be hidden
-
-#### Scenario BW-08c: Progress timeline
-
-- GIVEN a voorstel with 5 steps where steps 1-3 are completed, step 4 is active, step 5 is pending
-- THEN the progress indicator MUST show:
- - Steps 1-3: green checkmark with actor name and date
- - Step 4: blue active indicator with actor name and "Wachtend"
- - Step 5: grey pending indicator with actor name
-
----
-
-### REQ-BW-09: Besluit Registration
-
-When a besluit is received (from RIS or manually), the system MUST create a formal besluit record linked to the case via the ZGW Besluiten API pattern.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-09a: Manual besluit registration
-
-- GIVEN a voorstel has been treated by the college (outside Procest)
-- WHEN the secretariaat clicks "Besluit registreren" and enters: besluit tekst, ingangsdatum, besluittype
-- THEN a besluit object MUST be created via the BRC controller pattern
-- AND the besluit MUST be linked to the case (zaak-besluit relation)
-- AND the case activity timeline MUST show: "Besluit vastgesteld: [tekst]"
-
-#### Scenario BW-09b: Besluit with documents
-
-- GIVEN a besluit is being registered
-- WHEN the secretariaat attaches the besluitbrief and besluitenlijst
-- THEN the documents MUST be linked as besluitinformatieobjecten (via `BrcController`)
-- AND the documents MUST be stored in Nextcloud Files under the case folder
-
-#### Scenario BW-09c: Withdraw besluit
-
-- GIVEN a besluit has been registered but needs to be withdrawn
-- WHEN the secretariaat clicks "Intrekken" with reason "Ingetrokken door overheid"
-- THEN the besluit vervaldatum MUST be set to today
-- AND the vervalreden MUST be recorded
-- AND the case timeline MUST show: "Besluit ingetrokken: [reden]"
-
----
-
-### REQ-BW-10: Archiving
-
-Completed voorstellen and besluiten MUST be archived according to the Archiefwet requirements.
-
-**Feature tier**: V1
-
-
-#### Scenario BW-10a: Archive voorstel after besluit
-
-- GIVEN a voorstel has status "Besloten" with a linked besluit
-- WHEN the archiving process runs
-- THEN the voorstel document, all bijlagen, the audit trail, and the besluit document MUST be packaged
-- AND the package MUST be stored in the case's archive folder in Nextcloud Files
-- AND the voorstel status MUST change to "Gearchiveerd"
-
-#### Scenario BW-10b: Archive retention metadata
-
-- GIVEN an archived voorstel
-- THEN the archive record MUST include: bewaarplaats (Nextcloud Files path), bewaartermijn (from case type config), vernietigingsdatum (calculated from bewaar termijn)
-- AND the metadata MUST be queryable for future destruction scheduling
-
-## Dependencies
-
-- **Case Management spec** (`../case-management/spec.md`): Voorstellen originate from cases.
-- **Case Dashboard View spec** (`../case-dashboard-view/spec.md`): Voorstel panel on case detail.
-- **Roles & Decisions spec** (`../roles-decisions/spec.md`): Besluiten are created when the college decides.
-- **Task Management spec** (`../task-management/spec.md`): Parafering steps create tasks for actors.
-- **OpenRegister**: Voorstellen, parafeerroutes, parafeeracties stored as OpenRegister objects.
-- **OpenConnector**: iBabs API, NotuBiz API adapters for RIS integration.
-- **Docudesk**: Document templates for collegeadvies, raadsvoorstel.
-- **BrcController**: ZGW Besluiten API pattern for besluit registration (`lib/Controller/BrcController.php`).
-- **NotificatieService**: Nextcloud notifications for parafering tasks (`lib/Service/NotificatieService.php`).
-
-### Current Implementation Status
-
-**Not yet implemented.** No parafering, voorstel, or B&W decision-related code exists in the Procest codebase. There are no schemas for voorstel, parafeerroute, or parafeeractie in `procest_register.json`. No Vue components for parafering workflows exist.
-
-**Foundation available:**
-- Task management infrastructure (`src/views/tasks/`, `src/services/taskApi.js`, `src/utils/taskLifecycle.js`) provides a model for parafering steps (each step could be modeled as a task with custom type "parafering").
-- The `decision` schema exists in `SettingsService::SLUG_TO_CONFIG_KEY` (config key `decision_schema`), providing a foundation for recording besluiten.
-- The `decisionType` schema exists for typing decisions.
-- ZGW Besluiten API controller (`lib/Controller/BrcController.php`) handles besluit CRUD via ZGW API endpoints, including cross-register OIO sync and cascade delete.
-- Activity timeline component (`src/views/cases/components/ActivityTimeline.vue`) could display parafering events.
-- Nextcloud notification infrastructure is available via the `NotificatieService` (`lib/Service/NotificatieService.php`).
-- `CnDetailCard` component pattern for the voorstel panel on the case dashboard.
-- Case detail view (`CaseDetail.vue`) provides the mounting point for the B&W voorstellen panel.
-
-**Partial implementations:** The `BrcController` and decision schemas provide the data model foundation for step 9-10 (besluit registration and archiving).
-
-### Standards & References
-
-- **BPMN 2.0**: Process modeling standard for sequential/parallel parafeerroutes.
-- **CMMN 1.1**: HumanTask concept maps to parafering steps. Each step is a human task in a case plan model.
-- **ZGW Besluiten API (VNG)**: For recording formal besluiten (decisions) linked to cases. Procest's `BrcController` implements this standard.
-- **Awb (Algemene wet bestuursrecht)**: Legal framework for administrative decision-making.
-- **iBabs API**: Commercial API for raadsinformatiesysteem (council information system). REST-based with JSON payloads.
-- **NotuBiz API**: Alternative RIS platform API. Supports ZIP(XML+PDF) exchange format.
-- **GEMMA**: B&W besluitvormingsproces is a standard reference process in GEMMA zaakgericht werken.
-- **Archiefwet**: Legal requirements for archiving besluiten and voorstel documents.
-- **BIO**: Security requirements for handling voorstellen containing confidential information.
-
-### Specificity Assessment
-
-This spec is well-structured with a clear 10-step process model, defined OpenRegister schemas, and feature tier separation (V1/V2). The scenarios are detailed with concrete actor/action/system descriptions.
-
-**Strengths:** Clear process model with 10 steps, OpenRegister schema definitions for voorstel/parafeerroute/parafeeractie, concrete delegation scenario (namens), sequential and parallel parafering, admin route configuration, audit trail requirements, RIS connector patterns.
-
-**Resolved ambiguities:**
-- Parafeerroutes are stored as OpenRegister objects (not n8n workflows), enabling version tracking and admin UI.
-- The parafering dashboard is a separate navigation item (not a dashboard tab), with both secretariaat overview and personal inbox views.
-- Unavailable actors without delegates trigger escalation to the secretariaat after a configurable waiting period.
-- iBabs integration uses REST API via OpenConnector; NotuBiz supports both API and ZIP exchange.
-- Mandate/delegation is configured in admin settings and recorded in the audit trail with mandate reference.
-- Parallel parafering supports both "all" and "any" completion rules.
diff --git a/openspec/changes/case-dashboard-view/specs/case-dashboard-view/spec.md b/openspec/changes/case-dashboard-view/specs/case-dashboard-view/spec.md
deleted file mode 100644
index 375ce19f..00000000
--- a/openspec/changes/case-dashboard-view/specs/case-dashboard-view/spec.md
+++ /dev/null
@@ -1,101 +0,0 @@
----
-status: implemented
----
-# case-dashboard-view Specification
-
-## Purpose
-Deliver a polished, responsive, and accessible case detail dashboard that surfaces all relevant case context (status, deadlines, activity, tasks, documents, participants) on a single screen. This change closes the remaining MVP gaps in the existing `case-dashboard-view` capability: tablet/print rendering, per-panel skeleton loading, and a graceful 404 state for missing cases.
-
-## Context
-`CaseDetail.vue` already renders a two-column dashboard composed of panel cards (status timeline, info, deadline, activity, tasks, documents, participants). Tender demos and on-call inspections require the same screen to work on tablets, print cleanly, and signal loading state per-card instead of via a single page-level spinner. The capability is implemented end-to-end in code; this spec documents the contract.
-
-## Requirements
-
-### REQ-CDV-01c: Case Not Found State
-The dashboard MUST display a localized 404 empty state when the requested case cannot be loaded.
-
-#### Scenario CDV-01c-1: Unknown case identifier
-- GIVEN a user navigates to `/cases/does-not-exist`
-- WHEN the fetch resolves with no case object
-- THEN the view MUST render an `NcEmptyContent` with title "Zaak niet gevonden"
-- AND a "Terug naar overzicht" button MUST be visible
-- AND clicking the button MUST navigate the user to the case list view
-
-#### Scenario CDV-01c-2: Case fetch error
-- GIVEN the backend returns an error while loading a case
-- THEN the empty state MUST be shown instead of a broken panel layout
-- AND the action buttons in the page header MUST be hidden
-
-### REQ-CDV-01d: Per-Panel Skeleton Loading
-The dashboard MUST render a skeleton placeholder for each panel card while case data is loading.
-
-#### Scenario CDV-01d-1: Initial render with no data
-- GIVEN a user opens a case detail page for the first time
-- WHEN the case data has not yet resolved
-- THEN each panel card (status timeline, info, deadline, activity, tasks, documents, participants) MUST render a skeleton placeholder with animated bars
-- AND no global page-level spinner MUST be shown
-
-#### Scenario CDV-01d-2: Skeleton replaced on data ready
-- GIVEN the skeleton placeholders are visible
-- WHEN the case data resolves
-- THEN each skeleton MUST be replaced by its real panel content in place
-- AND the page MUST NOT flicker or fully re-mount
-
-### REQ-CDV-07b: Tablet Layout
-The dashboard MUST adapt to a single-column layout at tablet viewport widths.
-
-#### Scenario CDV-07b-1: Viewport at or below 1200px
-- GIVEN a user views the dashboard on a viewport of 1200px or narrower
-- THEN the panels MUST stack vertically in a single column
-- AND the order MUST be: status timeline, case info, deadline, activity, tasks, documents, participants
-- AND no horizontal scrollbar MUST appear on the page body
-
-#### Scenario CDV-07b-2: Touch target sizing
-- GIVEN the tablet layout is active
-- THEN all interactive controls (buttons, dropdowns, selects, status pills) MUST meet a minimum 44x44 CSS pixel touch target
-- AND focus rings MUST remain visible
-
-### REQ-CDV-07c: Print View
-The dashboard MUST render a clean, printable representation when the user prints the page.
-
-#### Scenario CDV-07c-1: Print stylesheet applied
-- GIVEN a user invokes the browser print dialog (e.g., Ctrl+P)
-- THEN `@media print` rules MUST hide navigation, action buttons, save/delete controls, and interactive dropdowns
-- AND backgrounds MUST be forced to white and shadows removed
-- AND the status timeline MUST render as a textual list (not interactive dots)
-
-#### Scenario CDV-07c-2: Print header includes identifier and date
-- GIVEN the dashboard is printed
-- THEN the printed output MUST include a header containing the case identifier and the current print date
-
-### REQ-CDV-DASH-01: Panel Composition
-The dashboard MUST present case data through a fixed set of panel cards.
-
-#### Scenario CDV-DASH-01-1: Default desktop layout
-- GIVEN a user opens a case detail page on a viewport wider than 1200px
-- THEN the dashboard MUST render a two-column layout with a primary column (status timeline, activity, documents) and a secondary column (case info, deadline, tasks, participants)
-- AND each panel MUST be rendered as a `CnDetailCard` (or equivalent panel component) with a clear title
-
-#### Scenario CDV-DASH-01-2: Empty panel handling
-- GIVEN a case has no tasks, documents, or participants
-- THEN each affected panel MUST render an inline empty state with a short Dutch label (e.g., "Geen taken", "Geen documenten", "Geen betrokkenen")
-- AND the panel MUST NOT be hidden entirely
-
-### REQ-CDV-DASH-02: Header Actions Visibility
-Page-level header actions MUST adapt to the dashboard's loading and error states.
-
-#### Scenario CDV-DASH-02-1: Actions during loading
-- GIVEN the dashboard is in skeleton loading state
-- THEN primary header actions (save, status change, delete) MUST be disabled or hidden
-- AND the back link MUST remain available
-
-#### Scenario CDV-DASH-02-2: Actions in not-found state
-- GIVEN the dashboard is in the "Zaak niet gevonden" state
-- THEN all header actions related to the case MUST be hidden
-- AND only the "Terug naar overzicht" navigation MUST be available
-
-## Dependencies
-- `CaseDetail.vue` (Procest) -- host view that composes the panels
-- `@nextcloud/vue` `NcEmptyContent`, `NcLoadingIcon` -- empty/loading primitives
-- `CnDetailCard` from `@conduction/nextcloud-vue` -- panel container
-- Case data store (object store + filesPlugin) -- supplies case, activity, tasks, documents, participants
diff --git a/openspec/changes/case-definition-portability/specs/case-definition-portability/spec.md b/openspec/changes/case-definition-portability/specs/case-definition-portability/spec.md
deleted file mode 100644
index d9ee1da1..00000000
--- a/openspec/changes/case-definition-portability/specs/case-definition-portability/spec.md
+++ /dev/null
@@ -1,352 +0,0 @@
----
-status: implemented
----
-# case-definition-portability Specification
-
-## Purpose
-Enable export and import of complete case type definitions (zaaktype configurations) as portable archives for DTAP (Development, Test, Acceptance, Production) pipeline deployment and inter-municipality sharing. A case definition package contains the schema, workflow definitions, form configurations, permission rules, and related settings. This eliminates manual recreation of case type configurations across environments and enables a marketplace of reusable zaaktype templates.
-
-## Context
-Mature case management platforms package complete case definitions into portable archives for cross-environment deployment. CaseFabric supports both definition migration and live migration of running cases when definitions change, using event-sourced migration with plan item matching, case file migration, and team migration -- all with full audit trails. Flowable exports CMMN/BPMN/DMN models as versioned deployment archives. Our approach focuses on versioned definition packages that map to OpenRegister schemas and n8n workflows, with explicit conflict resolution and environment parameterization.
-
-## Requirements
-
-### Requirement 1: Case definition export as portable package
-A complete zaaktype configuration MUST be exportable as a single ZIP archive containing all components.
-
-#### Scenario 1.1: Export a complete case definition
-- GIVEN zaaktype `omgevingsvergunning` is fully configured with:
- - OpenRegister schemas (case type fields, property definitions, validations)
- - n8n workflow definitions (intake, assessment, decision flows)
- - Status types and transitions (ordered statuses with `isFinal`, `notifyInitiator` flags)
- - Resultaattypen and besluittypen
- - Role type configuration (roltypen)
- - Document type templates
- - ZGW mapping configuration
-- WHEN an admin clicks "Exporteren" on the zaaktype in `CaseTypeDetail.vue`
-- THEN a ZIP archive MUST be downloaded containing:
- - `manifest.json` -- version, export date, source environment, Procest version, dependency list
- - `case-type.json` -- the caseType object with all properties
- - `schemas/` directory -- OpenRegister schema definitions for all related schemas
- - `statuses.json` -- ordered status types with transitions
- - `results.json` -- result type definitions
- - `decisions.json` -- decision type definitions
- - `roles.json` -- role type definitions
- - `documents.json` -- document type definitions
- - `properties.json` -- property definitions (custom fields)
- - `workflows/` directory -- n8n workflow JSON exports
- - `mappings.json` -- ZGW mapping configuration
- - `permissions.json` -- role-based access configuration
-
-#### Scenario 1.2: Export includes version information
-- GIVEN a case definition has been exported before as version "1.0.0"
-- AND the admin has since added 2 new status types and modified a property definition
-- WHEN the definition is exported again
-- THEN the manifest MUST show version "1.1.0" (auto-incremented minor version)
-- AND the manifest MUST include a `changelog` array listing changes since the previous version
-- AND the version MUST follow semantic versioning (major.minor.patch)
-
-#### Scenario 1.3: Export captures dependencies
-- GIVEN zaaktype `omgevingsvergunning` references a shared `person` schema for zaakbetrokkenen
-- WHEN the definition is exported
-- THEN the `manifest.json` MUST list `person` as an external dependency with its schema identifier
-- AND the `person` schema MUST NOT be included in the archive (it is shared, not owned by this zaaktype)
-- AND the manifest MUST specify the minimum compatible version of the `person` schema
-
-#### Scenario 1.4: Export via CLI
-- GIVEN an admin with shell access to the Nextcloud server
-- WHEN they run `docker exec nextcloud php occ procest:export-definition omgevingsvergunning --output /tmp/export.zip`
-- THEN the same ZIP archive MUST be produced as from the UI export
-- AND the command MUST support `--version` to set a specific version number
-
-#### Scenario 1.5: Export sanitizes environment-specific data
-- GIVEN an n8n workflow contains a webhook URL `https://test.gemeente.nl/api/intake`
-- AND the Procest register has ID `42` in the source environment
-- WHEN the definition is exported
-- THEN the webhook URL MUST be replaced with `{{BASE_URL}}/api/intake`
-- AND OpenRegister IDs MUST be replaced with slugs/identifiers (not numeric IDs)
-- AND API keys, credentials, and secrets MUST be stripped from workflow definitions
-
-### Requirement 2: Case definition import into another environment
-An exported package MUST be importable into a different Nextcloud instance with validation and conflict resolution.
-
-#### Scenario 2.1: Import into clean environment
-- GIVEN a target environment has OpenRegister and Procest installed but no case types configured
-- WHEN an admin uploads the `omgevingsvergunning.zip` package via the import wizard in `CaseTypeAdmin.vue`
-- THEN the system MUST create:
- - The caseType object in OpenRegister
- - All status types, result types, decision types, role types, document types, and property definitions
- - n8n workflows via the n8n API (`n8n_create_workflow` MCP tool)
- - ZGW mapping configuration
-- AND the import MUST report success/failure for each component in a results table
-- AND all created objects MUST reference each other correctly (no broken links)
-
-#### Scenario 2.2: Import with existing dependency resolution
-- GIVEN the package depends on a `person` schema that already exists in the target environment
-- WHEN importing, the system detects the existing `person` schema by matching on slug/identifier
-- THEN it MUST map the reference to the existing schema
-- AND it MUST NOT create a duplicate `person` schema
-- AND the mapping MUST be shown in the import preview: "person schema -> existing (ID: 78)"
-
-#### Scenario 2.3: Import conflict detection and resolution
-- GIVEN the target environment already has an `omgevingsvergunning` zaaktype
-- WHEN importing a package with the same zaaktype identifier
-- THEN the system MUST show a conflict report with a side-by-side diff of differences
-- AND offer resolution options per conflicting field:
- - **Keep existing** -- retain the target's value
- - **Use imported** -- overwrite with the package's value
- - **Merge** -- for array fields (e.g., status types), combine both sets
-- AND the admin MUST explicitly confirm each resolution before import proceeds
-
-#### Scenario 2.4: Import prompts for environment variables
-- GIVEN the package contains parameterized values (`{{BASE_URL}}`, `{{SMTP_HOST}}`)
-- WHEN the import wizard reaches the environment configuration step
-- THEN it MUST prompt the admin to provide values for each parameter
-- AND provide sensible defaults where detectable (e.g., current instance URL for `{{BASE_URL}}`)
-- AND validate that all parameters are filled before allowing import
-
-#### Scenario 2.5: Import rollback on failure
-- GIVEN an import is in progress and has created 5 of 8 components
-- WHEN the 6th component fails (e.g., n8n workflow creation fails due to missing node type)
-- THEN the system MUST roll back all 5 previously created components
-- AND report the specific failure with actionable error message
-- AND leave the target environment in its pre-import state
-
-### Requirement 3: Package validation before import
-Before applying an import, the package MUST be validated for completeness, compatibility, and correctness.
-
-#### Scenario 3.1: Structural validation
-- GIVEN an admin uploads a case definition package
-- WHEN the system validates the package structure
-- THEN it MUST verify:
- - `manifest.json` is present and valid JSON
- - All files referenced in the manifest exist in the archive
- - JSON files are syntactically valid
- - Required fields are present in each component file
-
-#### Scenario 3.2: Dependency validation
-- GIVEN the package references a `subsidy-rules` schema as an external dependency
-- AND `subsidy-rules` does not exist in the target environment
-- THEN the validation MUST report: "Ontbrekende afhankelijkheid: schema 'subsidy-rules'"
-- AND the import MUST be blocked until the dependency is resolved (install the schema or remove the reference)
-
-#### Scenario 3.3: Version compatibility validation
-- GIVEN the package was exported from Procest v2.5.0
-- AND the target environment runs Procest v2.3.0
-- THEN the validation MUST check the `minProcestVersion` field in the manifest
-- AND if incompatible, report: "Pakket vereist Procest v2.5.0 of hoger. Huidige versie: v2.3.0"
-
-#### Scenario 3.4: n8n workflow validation
-- GIVEN the package contains 3 n8n workflow JSON files
-- WHEN validating
-- THEN the system MUST verify each workflow JSON is a valid n8n workflow structure
-- AND check that all referenced n8n node types are available in the target n8n instance (via `search_nodes` MCP tool)
-- AND report missing node types as warnings (not blocking)
-
-#### Scenario 3.5: Validation report presentation
-- GIVEN validation completes with 2 errors and 3 warnings
-- THEN the import wizard MUST show a validation report with:
- - Errors (blocking): red, with explanation and suggested fix
- - Warnings (non-blocking): yellow, with explanation
- - Passed checks: green, collapsed by default
-- AND the "Import" button MUST be disabled until all errors are resolved
-
-### Requirement 4: Environment-agnostic packaging
-Connection strings, URLs, and environment-specific values MUST be parameterized in exported packages.
-
-#### Scenario 4.1: URL parameterization in workflows
-- GIVEN an n8n workflow contains webhook URL `https://test.gemeente.nl/api/intake`
-- WHEN the workflow is exported
-- THEN URLs matching known patterns (the current instance URL) MUST be auto-detected and replaced with `{{BASE_URL}}/api/intake`
-- AND the manifest MUST list `BASE_URL` as a required parameter with a description
-
-#### Scenario 4.2: Credential stripping
-- GIVEN an n8n workflow references a credential named "SMTP Production"
-- WHEN the workflow is exported
-- THEN the credential reference MUST be preserved as a named placeholder
-- AND the actual credential values (passwords, API keys) MUST be stripped
-- AND the import wizard MUST prompt the admin to map the credential to an existing credential in the target environment
-
-#### Scenario 4.3: OpenRegister ID remapping
-- GIVEN the source environment has register ID `42` and schema IDs `101, 102, 103`
-- WHEN the definition is exported
-- THEN all numeric IDs MUST be replaced with stable identifiers (slugs)
-- AND during import, the system MUST resolve slugs to the target environment's IDs
-- AND if a slug cannot be resolved, the import MUST report the specific unresolvable reference
-
-#### Scenario 4.4: Multi-environment parameter profiles
-- GIVEN a municipality has DTAP environments (Development, Test, Acceptance, Production)
-- WHEN importing the same package into each environment
-- THEN the import wizard MUST support saving parameter profiles (e.g., "Test", "Production")
-- AND previously used parameter values MUST be pre-filled when re-importing an updated package version
-
-### Requirement 5: Selective component export and import
-Admins MUST be able to choose which parts of a definition to export or import.
-
-#### Scenario 5.1: Export only schema and statuses
-- GIVEN zaaktype `omgevingsvergunning` has schemas, workflows, statuses, results, decisions, and permissions
-- WHEN an admin opens the export dialog and deselects workflows, results, decisions, and permissions
-- THEN the ZIP MUST contain only `case-type.json`, `schemas/`, `statuses.json`, `properties.json`, and `manifest.json`
-- AND the manifest MUST note which components were excluded
-- AND excluded components MUST NOT appear as dependencies
-
-#### Scenario 5.2: Import only workflows into existing definition
-- GIVEN an existing `omgevingsvergunning` zaaktype in the target environment
-- AND a package containing updated workflow definitions
-- WHEN the admin imports with only "Workflows" selected
-- THEN only the n8n workflows MUST be created/updated
-- AND the existing statuses, schemas, and other components MUST NOT be modified
-
-#### Scenario 5.3: Import individual component from package
-- GIVEN a package with 8 components
-- WHEN the import wizard shows the component list
-- THEN each component MUST have a checkbox (selected by default)
-- AND the admin MUST be able to deselect individual components
-- AND the system MUST warn if deselecting a component that others depend on
-
-#### Scenario 5.4: Export as ZGW Catalogi format
-- GIVEN an admin wants to share the zaaktype with a non-Procest system
-- WHEN they select "Exporteren als ZGW Catalogi" in the export dialog
-- THEN the export MUST produce a JSON file conforming to the ZGW Catalogi API schema (ZaakType, StatusType, ResultaatType, etc.)
-- AND this format MUST be importable by any ZGW-compatible system
-
-### Requirement 6: Definition versioning and change tracking
-Case definitions MUST be versioned with a change history to support controlled DTAP deployment.
-
-#### Scenario 6.1: Automatic version tracking
-- GIVEN zaaktype `omgevingsvergunning` at version "1.2.0"
-- WHEN the admin modifies a status type (changes the name from "Beoordeling" to "Inhoudelijke beoordeling")
-- AND saves the zaaktype
-- THEN the definition version MUST auto-increment to "1.2.1" (patch for minor change)
-- AND the change MUST be recorded: `{"field": "statusType.name", "old": "Beoordeling", "new": "Inhoudelijke beoordeling", "user": "admin", "date": "..."}`
-
-#### Scenario 6.2: Version comparison
-- GIVEN two exported packages: `omgevingsvergunning-v1.2.0.zip` and `omgevingsvergunning-v1.3.0.zip`
-- WHEN an admin uploads both for comparison
-- THEN the system MUST show a structured diff:
- - Added components (green)
- - Removed components (red)
- - Modified components (yellow, with field-level diff)
-
-#### Scenario 6.3: Version pinning for running cases
-- GIVEN 50 active cases using zaaktype `omgevingsvergunning` v1.2.0
-- WHEN the admin imports v1.3.0 (which adds a new required status)
-- THEN existing running cases MUST continue using v1.2.0 rules
-- AND only new cases MUST use v1.3.0
-- AND the admin MUST be able to manually migrate individual running cases to v1.3.0
-
-#### Scenario 6.4: Version rollback
-- GIVEN zaaktype `omgevingsvergunning` was updated from v1.2.0 to v1.3.0
-- AND issues are discovered with v1.3.0
-- WHEN the admin triggers rollback
-- THEN v1.3.0 MUST be deactivated (no new cases can use it)
-- AND v1.2.0 MUST be re-activated as the current version
-- AND running v1.3.0 cases MUST be flagged for review
-
-#### Scenario 6.5: Export version history
-- GIVEN zaaktype `omgevingsvergunning` has versions 1.0.0 through 1.5.0
-- WHEN the admin views the version history
-- THEN all versions MUST be listed with: version number, date, author, and change summary
-- AND any historical version MUST be downloadable as a ZIP package
-
-### Requirement 7: Live case migration between definition versions
-Running cases MUST be migratable to a new definition version without data loss.
-
-#### Scenario 7.1: Migrate case to new definition version
-- GIVEN case `zaak-1` is running on zaaktype `omgevingsvergunning` v1.2.0
-- AND v1.3.0 adds a new required property "milieu_categorie" and renames status "Beoordeling" to "Inhoudelijke beoordeling"
-- WHEN the admin triggers migration of `zaak-1` to v1.3.0
-- THEN the case's current status MUST be mapped to the new status name
-- AND the new required property MUST be added with a null/default value (flagged for case worker to fill)
-- AND removed properties from v1.3.0 MUST be archived (preserved but hidden)
-- AND the migration MUST be recorded in the case audit trail
-
-#### Scenario 7.2: Bulk migration with preview
-- GIVEN 50 cases running on v1.2.0
-- WHEN the admin triggers bulk migration to v1.3.0
-- THEN the system MUST first show a preview: "50 zaken worden gemigreerd. 3 zaken hebben status 'Beoordeling' die wordt hernoemd. 12 zaken missen het nieuwe veld 'milieu_categorie'."
-- AND the admin MUST confirm before migration proceeds
-- AND migration MUST be executed as a background job with progress tracking
-
-#### Scenario 7.3: Migration conflict for removed status
-- GIVEN case `zaak-2` has status "Vooronderzoek" which was removed in v1.3.0
-- WHEN migration is attempted
-- THEN the system MUST flag `zaak-2` as requiring manual intervention
-- AND the admin MUST map the removed status to an existing v1.3.0 status before migration can proceed
-
-#### Scenario 7.4: Migration preserves task state
-- GIVEN case `zaak-1` has 3 active tasks
-- WHEN migrated to v1.3.0
-- THEN existing tasks MUST be preserved with their current state and assignees
-- AND tasks referencing removed properties or statuses MUST be flagged for review
-
-### Requirement 8: Inter-municipality sharing
-Case definitions MUST be shareable between municipalities via a registry or direct exchange.
-
-#### Scenario 8.1: Publish to shared registry
-- GIVEN a municipality has a well-tested `woo-verzoek` zaaktype
-- WHEN the admin clicks "Publiceren naar bibliotheek" (publish to library)
-- THEN the definition package MUST be uploaded to a shared registry (OpenCatalogi or a dedicated Procest template registry)
-- AND the listing MUST include: name, description, version, municipality of origin, and screenshot
-
-#### Scenario 8.2: Browse and install from registry
-- GIVEN the Procest template library shows 15 available zaaktype templates
-- WHEN an admin searches for "WOO" and finds the published `woo-verzoek` template
-- THEN they MUST be able to preview the template's components (statuses, properties, workflows)
-- AND install it into their environment using the standard import flow
-
-#### Scenario 8.3: Template rating and feedback
-- GIVEN a municipality installed a shared template
-- THEN they MUST be able to rate the template (1-5 stars) and leave feedback
-- AND the rating MUST be visible to other municipalities browsing the registry
-
-### Requirement 9: Import/export audit trail
-All import and export operations MUST be logged for compliance and troubleshooting.
-
-#### Scenario 9.1: Export audit entry
-- GIVEN an admin exports zaaktype `omgevingsvergunning`
-- THEN an audit entry MUST be created with: user, timestamp, zaaktype, version, and components included
-
-#### Scenario 9.2: Import audit entry
-- GIVEN an admin imports a case definition package
-- THEN an audit entry MUST record: user, timestamp, package name, version, source environment, components imported, and conflict resolutions applied
-
-#### Scenario 9.3: Migration audit entry
-- GIVEN 50 cases are migrated from v1.2.0 to v1.3.0
-- THEN an audit entry MUST record: user, timestamp, source version, target version, number of cases migrated, number of cases requiring manual intervention, and any errors
-
-## Dependencies
-- OpenRegister (for case type and schema storage, ConfigurationService for import)
-- n8n MCP (for workflow export/import via `n8n_get_workflow`, `n8n_create_workflow`)
-- OpenCatalogi (optional, for shared template registry)
-- ZGW Catalogi API (optional, for interoperable export format)
-- Nextcloud background jobs (for bulk migration processing)
-
----
-
-### Current Implementation Status
-
-**Not yet implemented.** No export/import functionality for case type definitions exists in the codebase. There are no controllers, services, or UI components for definition portability.
-
-**Foundation available:**
-- `SettingsService::loadConfiguration()` (`lib/Service/SettingsService.php`) imports register configuration from `procest_register.json` via OpenRegister's `ConfigurationService::importFromApp()`. This import/auto-configure pattern serves as a model for case definition import.
-- The `procest_register.json` file (`lib/Settings/procest_register.json`) defines the complete schema structure for all case type entities, providing a reference format for portable definitions.
-- The repair steps `InitializeSettings` (`lib/Repair/InitializeSettings.php`) and `LoadDefaultZgwMappings` (`lib/Repair/LoadDefaultZgwMappings.php`) demonstrate import/initialization patterns.
-- OpenRegister's `ConfigurationService` has version-aware import with force-reimport capability.
-- n8n workflows can be exported/imported via n8n API (n8n MCP tools: `n8n_get_workflow`, `n8n_create_workflow`).
-- `CaseTypeDetail.vue` provides the UI integration point for export/import buttons.
-- `CaseTypeAdmin.vue` provides the list view where import and template library buttons would be added.
-
-**Partial implementations:** None.
-
-### Standards & References
-
-- **DTAP (Development, Test, Acceptance, Production)**: Standard software deployment pipeline that portability supports.
-- **ZGW Catalogi API (VNG)**: Case type definitions (ZaakType, StatusType, ResultaatType, etc.) follow ZGW Catalogi API schemas, which serve as an interoperable export format.
-- **GEMMA**: Dutch municipal architecture standard promoting reusable configurations across municipalities.
-- **CaseFabric Live Migration**: Reference architecture for migrating running cases between definition versions using event-sourced migration with plan item matching.
-- **Flowable Deployment Archives**: Reference for CMMN/BPMN/DMN model versioning and deployment packaging.
-- **OpenRegister Configuration Format**: The existing `procest_register.json` format provides a well-structured configuration exchange format.
-- **Common Ground**: Emphasizes configuration portability across municipalities via standardized APIs.
-- **Semantic Versioning (semver)**: Version numbering standard for definition packages.
-- **CMMN 1.1**: Case definitions map to CasePlanModel; export format should preserve CMMN semantics.
diff --git a/openspec/changes/case-email-integration/specs/case-email-integration/spec.md b/openspec/changes/case-email-integration/specs/case-email-integration/spec.md
index 07459858..aa5ce9e6 100644
--- a/openspec/changes/case-email-integration/specs/case-email-integration/spec.md
+++ b/openspec/changes/case-email-integration/specs/case-email-integration/spec.md
@@ -9,391 +9,390 @@ Send and receive email from within case context. Emails are converted to PDF and
## Context
Email remains a primary communication channel between municipalities and citizens/organizations. Currently, email communication happens outside the case system, making it impossible to reconstruct the full communication history. This spec integrates email directly into the case workflow: outbound emails use templates with case data, and all sent/received emails are archived as case documents. The integration leverages Nextcloud Mail app infrastructure where available, with a fallback to direct SMTP/IMAP for standalone deployments. All email data is stored as OpenRegister objects under the Procest register using dedicated schemas (`emailTemplate`, `emailMessage`, `emailThread`).
-## Requirements
-
-### Requirement 1: Send email from case context
+## ADDED Requirements
+### Requirement: Send email from case context
The system MUST support sending email from within a case, with the email stored as a case document and recorded in the activity timeline.
-#### Scenario 1.1: Send email with case template
-- GIVEN a case of type "Omgevingsvergunning" with configured email templates
-- WHEN the case worker selects template "Ontvangstbevestiging" and clicks send
-- THEN template variables (`{{zaakNummer}}`, `{{aanvragerNaam}}`, `{{startdatum}}`) MUST be resolved from case data
-- AND the email MUST be sent to the case's primary contact email address
-- AND a PDF copy of the sent email MUST be created via Docudesk and linked as a case document (schema `caseDocument`)
-- AND the case activity array MUST receive an entry of type `email_sent` with description "Email verzonden: Ontvangstbevestiging"
-
-#### Scenario 1.2: Send ad-hoc email without template
-- GIVEN a case with a linked contact email
-- WHEN the case worker composes a free-form email with subject and body
-- THEN the email MUST be sent with the municipality's configured from-address (stored in `IAppConfig` under key `email_from_address`)
-- AND the case identifier MUST be included in the email subject as a prefix (e.g., "[ZAAK-2026-001234] Uw aanvraag")
-- AND the sent email MUST be stored as a `caseDocument` object linked to the case
-
-#### Scenario 1.3: Send email with case document attachments
-- GIVEN a case with existing documents stored in OpenRegister
-- WHEN the case worker selects documents from the case's document list to attach
-- THEN each selected document MUST be retrieved from Nextcloud Files via `IRootFolder` and attached to the email
-- AND the total attachment size MUST NOT exceed the configured limit (default: 25 MB, stored in `IAppConfig` under key `email_max_attachment_size`)
-- AND if the size limit is exceeded, the UI MUST display a validation error before attempting to send
-
-#### Scenario 1.4: Send email with CC and BCC recipients
-- GIVEN a case with multiple participants (stored as `role` objects)
-- WHEN the case worker adds CC or BCC recipients from the participant list or by typing email addresses
-- THEN the email MUST be sent to all specified recipients
-- AND all recipients MUST be recorded in the stored email message object
-
-#### Scenario 1.5: Prevent sending from closed case
-- GIVEN a case whose current status has `isFinal === true`
-- WHEN the case worker attempts to send an email
-- THEN the email compose button MUST be disabled
-- AND a tooltip MUST explain that closed cases cannot send new correspondence
-
-### Requirement 2: Email templates per case type (zaaktype)
+#### Scenario: Send email with case template
+- **GIVEN** a case of type "Omgevingsvergunning" with configured email templates
+- **WHEN** the case worker selects template "Ontvangstbevestiging" and clicks send
+- **THEN** template variables (`{{zaakNummer}}`, `{{aanvragerNaam}}`, `{{startdatum}}`) MUST be resolved from case data
+- **AND** the email MUST be sent to the case's primary contact email address
+- **AND** a PDF copy of the sent email MUST be created via Docudesk and linked as a case document (schema `caseDocument`)
+- **AND** the case activity array MUST receive an entry of type `email_sent` with description "Email verzonden: Ontvangstbevestiging"
+
+#### Scenario: Send ad-hoc email without template
+- **GIVEN** a case with a linked contact email
+- **WHEN** the case worker composes a free-form email with subject and body
+- **THEN** the email MUST be sent with the municipality's configured from-address (stored in `IAppConfig` under key `email_from_address`)
+- **AND** the case identifier MUST be included in the email subject as a prefix (e.g., "[ZAAK-2026-001234] Uw aanvraag")
+- **AND** the sent email MUST be stored as a `caseDocument` object linked to the case
+
+#### Scenario: Send email with case document attachments
+- **GIVEN** a case with existing documents stored in OpenRegister
+- **WHEN** the case worker selects documents from the case's document list to attach
+- **THEN** each selected document MUST be retrieved from Nextcloud Files via `IRootFolder` and attached to the email
+- **AND** the total attachment size MUST NOT exceed the configured limit (default: 25 MB, stored in `IAppConfig` under key `email_max_attachment_size`)
+- **AND** if the size limit is exceeded, the UI MUST display a validation error before attempting to send
+
+#### Scenario: Send email with CC and BCC recipients
+- **GIVEN** a case with multiple participants (stored as `role` objects)
+- **WHEN** the case worker adds CC or BCC recipients from the participant list or by typing email addresses
+- **THEN** the email MUST be sent to all specified recipients
+- **AND** all recipients MUST be recorded in the stored email message object
+
+#### Scenario: Prevent sending from closed case
+- **GIVEN** a case whose current status has `isFinal === true`
+- **WHEN** the case worker attempts to send an email
+- **THEN** the email compose button MUST be disabled
+- **AND** a tooltip MUST explain that closed cases cannot send new correspondence
+
+### Requirement: Email templates per case type (zaaktype)
The system MUST support configurable email templates linked to case types, stored as OpenRegister objects under the `emailTemplate` schema.
-#### Scenario 2.1: Create email template for a case type
-- GIVEN the case type configuration screen (`CaseTypeDetail.vue`)
-- WHEN the admin creates a template with name, subject pattern, and HTML body containing `{{variable}}` placeholders
-- THEN the template MUST be saved as an OpenRegister object with schema `emailTemplate`
-- AND the template MUST reference the case type ID in its `caseType` field
-- AND the template MUST appear in the template selector when composing emails on cases of that type
-
-#### Scenario 2.2: Template variable resolution with preview
-- GIVEN a template with body "Beste {{aanvragerNaam}}, uw zaak {{zaakNummer}} is in behandeling genomen op {{startdatum}}."
-- WHEN the case worker previews the email before sending
-- THEN all variables MUST be resolved by looking up the case object's fields (title, identifier, startDate, assignee) and linked participant data
-- AND unresolved variables MUST be highlighted with a red background and a warning banner listing the unresolved variable names
-
-#### Scenario 2.3: Available variables sidebar
-- GIVEN the template editor or email compose view
-- WHEN the user views the variable reference panel
-- THEN it MUST list all available variables grouped by source: case fields (identifier, title, startDate, deadline, description), contact fields (name, email, phone, address), and case type fields (title, processingDeadline)
-- AND clicking a variable name MUST insert it at the cursor position in the editor
-
-#### Scenario 2.4: Template versioning
-- GIVEN an email template that has been used in previously sent emails
-- WHEN the admin modifies the template text
-- THEN the system MUST create a new version of the template rather than overwriting
-- AND previously sent emails MUST retain the template version they were sent with
-
-#### Scenario 2.5: Default templates
-- GIVEN a newly created case type with no custom templates
-- WHEN the admin views the templates tab
-- THEN the system MUST offer to create standard templates: "Ontvangstbevestiging" (acknowledgment), "Informatieverzoek" (information request), and "Besluit" (decision notification)
-
-### Requirement 3: Inbound email linking
+#### Scenario: Create email template for a case type
+- **GIVEN** the case type configuration screen (`CaseTypeDetail.vue`)
+- **WHEN** the admin creates a template with name, subject pattern, and HTML body containing `{{variable}}` placeholders
+- **THEN** the template MUST be saved as an OpenRegister object with schema `emailTemplate`
+- **AND** the template MUST reference the case type ID in its `caseType` field
+- **AND** the template MUST appear in the template selector when composing emails on cases of that type
+
+#### Scenario: Template variable resolution with preview
+- **GIVEN** a template with body "Beste {{aanvragerNaam}}, uw zaak {{zaakNummer}} is in behandeling genomen op {{startdatum}}."
+- **WHEN** the case worker previews the email before sending
+- **THEN** all variables MUST be resolved by looking up the case object's fields (title, identifier, startDate, assignee) and linked participant data
+- **AND** unresolved variables MUST be highlighted with a red background and a warning banner listing the unresolved variable names
+
+#### Scenario: Available variables sidebar
+- **GIVEN** the template editor or email compose view
+- **WHEN** the user views the variable reference panel
+- **THEN** it MUST list all available variables grouped by source: case fields (identifier, title, startDate, deadline, description), contact fields (name, email, phone, address), and case type fields (title, processingDeadline)
+- **AND** clicking a variable name MUST insert it at the cursor position in the editor
+
+#### Scenario: Template versioning
+- **GIVEN** an email template that has been used in previously sent emails
+- **WHEN** the admin modifies the template text
+- **THEN** the system MUST create a new version of the template rather than overwriting
+- **AND** previously sent emails MUST retain the template version they were sent with
+
+#### Scenario: Default templates
+- **GIVEN** a newly created case type with no custom templates
+- **WHEN** the admin views the templates tab
+- **THEN** the system MUST offer to create standard templates: "Ontvangstbevestiging" (acknowledgment), "Informatieverzoek" (information request), and "Besluit" (decision notification)
+
+### Requirement: Inbound email linking
The system MUST support linking incoming emails to cases, both automatically via case number detection and manually via a queue interface.
-#### Scenario 3.1: Auto-link by case number in subject
-- GIVEN an incoming email with subject "RE: [ZAAK-2026-001234] Uw aanvraag"
-- WHEN the inbound email handler processes the message
-- THEN the handler MUST extract the case number using regex pattern `\[([A-Z]+-\d{4}-\d{6})\]`
-- AND it MUST look up the case by identifier in OpenRegister using `_filters[identifier]=ZAAK-2026-001234`
-- AND the email MUST be converted to PDF via Docudesk and stored as a `caseDocument`
-- AND the case activity array MUST receive an entry of type `email_received` with the sender's email address
-
-#### Scenario 3.2: Auto-link by Message-ID threading
-- GIVEN an incoming email whose `In-Reply-To` header matches a previously sent email's `Message-ID`
-- WHEN the inbound handler processes the message
-- THEN it MUST look up the original email message object by `messageId` field
-- AND it MUST link the incoming email to the same case as the original
-
-#### Scenario 3.3: Manual email linking via queue
-- GIVEN an email that could not be auto-linked (no case number in subject, no matching thread)
-- WHEN the case worker views the unlinked email queue at route `/emails/unlinked`
-- THEN each unlinked email MUST display sender, subject, date, and body preview
-- AND the worker MUST be able to search for a case by identifier or title and link the email with one click
-- AND after linking, the email MUST be removed from the unlinked queue
-
-#### Scenario 3.4: Discard unlinked email
-- GIVEN an unlinked email that is spam or irrelevant
-- WHEN the case worker selects "Discard" on the email
-- THEN the email MUST be marked as discarded with a reason (optional)
-- AND it MUST be moved to a "Discarded" section, not permanently deleted
-
-#### Scenario 3.5: Inbound email notification
-- GIVEN a case with an assigned handler (assignee field)
-- WHEN a new email is linked to that case (automatically or manually)
-- THEN the handler MUST receive a Nextcloud notification via `INotificationManager` with a link to the case detail page
-
-### Requirement 4: Email threading
+#### Scenario: Auto-link by case number in subject
+- **GIVEN** an incoming email with subject "RE: [ZAAK-2026-001234] Uw aanvraag"
+- **WHEN** the inbound email handler processes the message
+- **THEN** the handler MUST extract the case number using regex pattern `\[([A-Z]+-\d{4}-\d{6})\]`
+- **AND** it MUST look up the case by identifier in OpenRegister using `_filters[identifier]=ZAAK-2026-001234`
+- **AND** the email MUST be converted to PDF via Docudesk and stored as a `caseDocument`
+- **AND** the case activity array MUST receive an entry of type `email_received` with the sender's email address
+
+#### Scenario: Auto-link by Message-ID threading
+- **GIVEN** an incoming email whose `In-Reply-To` header matches a previously sent email's `Message-ID`
+- **WHEN** the inbound handler processes the message
+- **THEN** it MUST look up the original email message object by `messageId` field
+- **AND** it MUST link the incoming email to the same case as the original
+
+#### Scenario: Manual email linking via queue
+- **GIVEN** an email that could not be auto-linked (no case number in subject, no matching thread)
+- **WHEN** the case worker views the unlinked email queue at route `/emails/unlinked`
+- **THEN** each unlinked email MUST display sender, subject, date, and body preview
+- **AND** the worker MUST be able to search for a case by identifier or title and link the email with one click
+- **AND** after linking, the email MUST be removed from the unlinked queue
+
+#### Scenario: Discard unlinked email
+- **GIVEN** an unlinked email that is spam or irrelevant
+- **WHEN** the case worker selects "Discard" on the email
+- **THEN** the email MUST be marked as discarded with a reason (optional)
+- **AND** it MUST be moved to a "Discarded" section, not permanently deleted
+
+#### Scenario: Inbound email notification
+- **GIVEN** a case with an assigned handler (assignee field)
+- **WHEN** a new email is linked to that case (automatically or manually)
+- **THEN** the handler MUST receive a Nextcloud notification via `INotificationManager` with a link to the case detail page
+
+### Requirement: Email threading
The system MUST maintain email thread context within cases using RFC 2822 Message-ID and In-Reply-To headers.
-#### Scenario 4.1: Outbound email creates thread
-- GIVEN a case with no existing email threads
-- WHEN the case worker sends the first email
-- THEN the system MUST generate a unique `Message-ID` header and store it in the `emailMessage` object
-- AND a new `emailThread` object MUST be created linking the message to the case
-
-#### Scenario 4.2: Reply links to existing thread
-- GIVEN a sent email with `Message-ID: ` on case ZAAK-2026-001234
-- WHEN a reply arrives with `In-Reply-To: `
-- THEN the reply MUST be added to the existing `emailThread` object
-- AND the thread's `messageCount` field MUST be incremented
-
-#### Scenario 4.3: View email thread chronologically
-- GIVEN a case with a 5-message email thread
-- WHEN the case worker opens the thread view in the case detail
-- THEN all messages MUST be displayed in chronological order (oldest first)
-- AND each message MUST show direction (inbound/outbound), sender, timestamp, subject, and body preview
-- AND inbound messages MUST have a distinct visual style (e.g., left-aligned) from outbound messages (right-aligned)
-
-#### Scenario 4.4: Multiple threads per case
-- GIVEN a case with two separate email conversations (e.g., one with the applicant, one with an advisor)
-- WHEN viewing the case's email tab
-- THEN each thread MUST be displayed as a collapsible group with thread subject as header
-- AND threads MUST be sorted by most recent message date descending
-
-#### Scenario 4.5: Thread subject line consistency
-- GIVEN an ongoing email thread with subject "[ZAAK-2026-001234] Omgevingsvergunning"
-- WHEN the case worker replies within the thread
-- THEN the reply MUST preserve the original subject line with "RE:" prefix
-- AND the `In-Reply-To` header MUST reference the previous message's `Message-ID`
-
-### Requirement 5: Email-to-PDF conversion
+#### Scenario: Outbound email creates thread
+- **GIVEN** a case with no existing email threads
+- **WHEN** the case worker sends the first email
+- **THEN** the system MUST generate a unique `Message-ID` header and store it in the `emailMessage` object
+- **AND** a new `emailThread` object MUST be created linking the message to the case
+
+#### Scenario: Reply links to existing thread
+- **GIVEN** a sent email with `Message-ID: ` on case ZAAK-2026-001234
+- **WHEN** a reply arrives with `In-Reply-To: `
+- **THEN** the reply MUST be added to the existing `emailThread` object
+- **AND** the thread's `messageCount` field MUST be incremented
+
+#### Scenario: View email thread chronologically
+- **GIVEN** a case with a 5-message email thread
+- **WHEN** the case worker opens the thread view in the case detail
+- **THEN** all messages MUST be displayed in chronological order (oldest first)
+- **AND** each message MUST show direction (inbound/outbound), sender, timestamp, subject, and body preview
+- **AND** inbound messages MUST have a distinct visual style (e.g., left-aligned) from outbound messages (right-aligned)
+
+#### Scenario: Multiple threads per case
+- **GIVEN** a case with two separate email conversations (e.g., one with the applicant, one with an advisor)
+- **WHEN** viewing the case's email tab
+- **THEN** each thread MUST be displayed as a collapsible group with thread subject as header
+- **AND** threads MUST be sorted by most recent message date descending
+
+#### Scenario: Thread subject line consistency
+- **GIVEN** an ongoing email thread with subject "[ZAAK-2026-001234] Omgevingsvergunning"
+- **WHEN** the case worker replies within the thread
+- **THEN** the reply MUST preserve the original subject line with "RE:" prefix
+- **AND** the `In-Reply-To` header MUST reference the previous message's `Message-ID`
+
+### Requirement: Email-to-PDF conversion
All emails MUST be converted to PDF for archival as case documents, using Docudesk for PDF generation.
-#### Scenario 5.1: Convert sent email to PDF
-- GIVEN a sent email with HTML body and 2 attachments
-- WHEN the email is stored as a case document
-- THEN Docudesk MUST generate a PDF that includes email headers (from, to, cc, date, subject) at the top
-- AND the HTML body MUST be rendered as formatted text in the PDF
-- AND attachments MUST be listed by filename and size at the end of the PDF (not embedded)
-
-#### Scenario 5.2: Convert received email to PDF
-- GIVEN a received email with plain-text body
-- WHEN the inbound handler processes the email
-- THEN the plain text MUST be rendered in the PDF with proper line wrapping
-- AND any inline images MUST be embedded in the PDF
-
-#### Scenario 5.3: PDF stored in case folder
-- GIVEN a case with identifier ZAAK-2026-001234
-- WHEN an email PDF is created
-- THEN the PDF MUST be stored in Nextcloud Files at path `Procest/ZAAK-2026-001234/Correspondentie/{date}_{subject}.pdf`
-- AND the file MUST be registered as a `caseDocument` object in OpenRegister linking the file path and the case ID
-
-#### Scenario 5.4: Conversion failure handling
-- GIVEN that Docudesk is unavailable or returns an error during PDF conversion
-- WHEN the system attempts to convert an email
-- THEN the email message object MUST still be saved in OpenRegister with `pdfStatus: 'failed'`
-- AND a background job MUST retry the conversion up to 3 times with exponential backoff
-- AND the case worker MUST see a warning icon on the email indicating PDF conversion pending
-
-#### Scenario 5.5: Large email handling
-- GIVEN an incoming email with body exceeding 5 MB (e.g., large HTML with embedded images)
-- WHEN the inbound handler processes the email
-- THEN the email MUST still be processed and linked to the case
-- AND the PDF conversion MUST be delegated to a background job rather than processed synchronously
-
-### Requirement 6: Email compose UI component
+#### Scenario: Convert sent email to PDF
+- **GIVEN** a sent email with HTML body and 2 attachments
+- **WHEN** the email is stored as a case document
+- **THEN** Docudesk MUST generate a PDF that includes email headers (from, to, cc, date, subject) at the top
+- **AND** the HTML body MUST be rendered as formatted text in the PDF
+- **AND** attachments MUST be listed by filename and size at the end of the PDF (not embedded)
+
+#### Scenario: Convert received email to PDF
+- **GIVEN** a received email with plain-text body
+- **WHEN** the inbound handler processes the email
+- **THEN** the plain text MUST be rendered in the PDF with proper line wrapping
+- **AND** any inline images MUST be embedded in the PDF
+
+#### Scenario: PDF stored in case folder
+- **GIVEN** a case with identifier ZAAK-2026-001234
+- **WHEN** an email PDF is created
+- **THEN** the PDF MUST be stored in Nextcloud Files at path `Procest/ZAAK-2026-001234/Correspondentie/{date}_{subject}.pdf`
+- **AND** the file MUST be registered as a `caseDocument` object in OpenRegister linking the file path and the case ID
+
+#### Scenario: Conversion failure handling
+- **GIVEN** that Docudesk is unavailable or returns an error during PDF conversion
+- **WHEN** the system attempts to convert an email
+- **THEN** the email message object MUST still be saved in OpenRegister with `pdfStatus: 'failed'`
+- **AND** a background job MUST retry the conversion up to 3 times with exponential backoff
+- **AND** the case worker MUST see a warning icon on the email indicating PDF conversion pending
+
+#### Scenario: Large email handling
+- **GIVEN** an incoming email with body exceeding 5 MB (e.g., large HTML with embedded images)
+- **WHEN** the inbound handler processes the email
+- **THEN** the email MUST still be processed and linked to the case
+- **AND** the PDF conversion MUST be delegated to a background job rather than processed synchronously
+
+### Requirement: Email compose UI component
The case detail view MUST include an email composition interface accessible from the case detail page.
-#### Scenario 6.1: Open email composer from case detail
-- GIVEN the case detail view (`CaseDetail.vue`) with a non-final status
-- WHEN the case worker clicks "Send email" in the case actions
-- THEN a modal dialog MUST open with fields for: recipient (pre-filled from case contact), CC, BCC, subject (pre-filled with case identifier prefix), body (rich text editor), template selector, and attachment picker
-
-#### Scenario 6.2: Rich text editor for email body
-- GIVEN the email compose dialog is open
-- WHEN the case worker types in the body field
-- THEN the editor MUST support bold, italic, links, bulleted lists, and numbered lists
-- AND the editor MUST use the Nextcloud text editor component or a compatible WYSIWYG
-
-#### Scenario 6.3: Attachment picker from case documents
-- GIVEN the email compose dialog is open
-- WHEN the case worker clicks "Attach document"
-- THEN a document picker MUST display the case's existing documents (fetched from `caseDocument` objects)
-- AND the worker MUST be able to select multiple documents
-- AND the running total attachment size MUST be displayed below the attachment list
-
-#### Scenario 6.4: Template selector pre-fills body and subject
-- GIVEN the email compose dialog is open and the case has a case type with configured templates
-- WHEN the case worker selects a template from the dropdown
-- THEN the subject and body fields MUST be pre-filled with the template's content
-- AND template variables MUST be resolved immediately with case data
-- AND the worker MUST be able to edit the pre-filled content before sending
-
-#### Scenario 6.5: Send confirmation
-- GIVEN the email compose form is filled out
-- WHEN the case worker clicks "Send"
-- THEN a confirmation dialog MUST appear showing recipient count and attachment count
-- AND after confirmation, the email MUST be sent and the compose dialog MUST close
-- AND the case activity timeline MUST refresh to show the new email event
-
-### Requirement 7: Inbound email polling background job
+#### Scenario: Open email composer from case detail
+- **GIVEN** the case detail view (`CaseDetail.vue`) with a non-final status
+- **WHEN** the case worker clicks "Send email" in the case actions
+- **THEN** a modal dialog MUST open with fields for: recipient (pre-filled from case contact), CC, BCC, subject (pre-filled with case identifier prefix), body (rich text editor), template selector, and attachment picker
+
+#### Scenario: Rich text editor for email body
+- **GIVEN** the email compose dialog is open
+- **WHEN** the case worker types in the body field
+- **THEN** the editor MUST support bold, italic, links, bulleted lists, and numbered lists
+- **AND** the editor MUST use the Nextcloud text editor component or a compatible WYSIWYG
+
+#### Scenario: Attachment picker from case documents
+- **GIVEN** the email compose dialog is open
+- **WHEN** the case worker clicks "Attach document"
+- **THEN** a document picker MUST display the case's existing documents (fetched from `caseDocument` objects)
+- **AND** the worker MUST be able to select multiple documents
+- **AND** the running total attachment size MUST be displayed below the attachment list
+
+#### Scenario: Template selector pre-fills body and subject
+- **GIVEN** the email compose dialog is open and the case has a case type with configured templates
+- **WHEN** the case worker selects a template from the dropdown
+- **THEN** the subject and body fields MUST be pre-filled with the template's content
+- **AND** template variables MUST be resolved immediately with case data
+- **AND** the worker MUST be able to edit the pre-filled content before sending
+
+#### Scenario: Send confirmation
+- **GIVEN** the email compose form is filled out
+- **WHEN** the case worker clicks "Send"
+- **THEN** a confirmation dialog MUST appear showing recipient count and attachment count
+- **AND** after confirmation, the email MUST be sent and the compose dialog MUST close
+- **AND** the case activity timeline MUST refresh to show the new email event
+
+### Requirement: Inbound email polling background job
The system MUST poll configured IMAP mailboxes for new emails using Nextcloud's `IJobList` background job infrastructure.
-#### Scenario 7.1: Register background job on app enable
-- GIVEN the Procest app is enabled and IMAP settings are configured
-- WHEN the app registers its background jobs
-- THEN an `InboundEmailJob` MUST be registered with `IJobList` as a `TimedJob` with configurable interval (default: 5 minutes, stored in `IAppConfig` key `email_poll_interval`)
-
-#### Scenario 7.2: Poll IMAP mailbox for new messages
-- GIVEN the background job runs
-- WHEN it connects to the configured IMAP server
-- THEN it MUST fetch all unread messages from the configured folder (default: INBOX)
-- AND for each message, it MUST attempt auto-linking by subject and thread headers
-- AND successfully processed messages MUST be moved to a "Processed" IMAP folder
-
-#### Scenario 7.3: IMAP connection failure
-- GIVEN the IMAP server is unreachable
-- WHEN the background job attempts to connect
-- THEN it MUST log the failure via `LoggerInterface` at error level
-- AND it MUST NOT throw an exception that would deregister the job
-- AND the next scheduled run MUST proceed normally
-
-#### Scenario 7.4: Rate limiting
-- GIVEN a large mailbox with 500 unread messages
-- WHEN the background job processes messages
-- THEN it MUST process at most 50 messages per run (configurable via `email_poll_batch_size`)
-- AND remaining messages MUST be picked up in subsequent runs
-
-#### Scenario 7.5: Duplicate detection
-- GIVEN an email that has already been processed (its `Message-ID` exists in the `emailMessage` objects)
-- WHEN the background job encounters the same email again (e.g., not moved due to IMAP error)
-- THEN it MUST skip the duplicate and mark it as processed
-- AND it MUST NOT create a duplicate case document
-
-### Requirement 8: SMTP and IMAP configuration
+#### Scenario: Register background job on app enable
+- **GIVEN** the Procest app is enabled and IMAP settings are configured
+- **WHEN** the app registers its background jobs
+- **THEN** an `InboundEmailJob` MUST be registered with `IJobList` as a `TimedJob` with configurable interval (default: 5 minutes, stored in `IAppConfig` key `email_poll_interval`)
+
+#### Scenario: Poll IMAP mailbox for new messages
+- **GIVEN** the background job runs
+- **WHEN** it connects to the configured IMAP server
+- **THEN** it MUST fetch all unread messages from the configured folder (default: INBOX)
+- **AND** for each message, it MUST attempt auto-linking by subject and thread headers
+- **AND** successfully processed messages MUST be moved to a "Processed" IMAP folder
+
+#### Scenario: IMAP connection failure
+- **GIVEN** the IMAP server is unreachable
+- **WHEN** the background job attempts to connect
+- **THEN** it MUST log the failure via `LoggerInterface` at error level
+- **AND** it MUST NOT throw an exception that would deregister the job
+- **AND** the next scheduled run MUST proceed normally
+
+#### Scenario: Rate limiting
+- **GIVEN** a large mailbox with 500 unread messages
+- **WHEN** the background job processes messages
+- **THEN** it MUST process at most 50 messages per run (configurable via `email_poll_batch_size`)
+- **AND** remaining messages MUST be picked up in subsequent runs
+
+#### Scenario: Duplicate detection
+- **GIVEN** an email that has already been processed (its `Message-ID` exists in the `emailMessage` objects)
+- **WHEN** the background job encounters the same email again (e.g., not moved due to IMAP error)
+- **THEN** it MUST skip the duplicate and mark it as processed
+- **AND** it MUST NOT create a duplicate case document
+
+### Requirement: SMTP and IMAP configuration
The admin settings MUST provide configuration for outbound SMTP and inbound IMAP server settings.
-#### Scenario 8.1: Configure SMTP settings
-- GIVEN the Procest admin settings page (`Settings.vue` or dedicated email tab)
-- WHEN the admin enters SMTP host, port, encryption (none/STARTTLS/SSL), username, password, and from-address
-- THEN the settings MUST be stored in `IAppConfig` under keys prefixed with `email_smtp_`
-- AND the password MUST be stored encrypted using `ISecureRandom` or Nextcloud's credential store
-
-#### Scenario 8.2: Test SMTP connection
-- GIVEN SMTP settings are configured
-- WHEN the admin clicks "Send test email"
-- THEN the system MUST attempt to send a test email to the admin's email address
-- AND on success, a green "Connection successful" message MUST appear
-- AND on failure, the specific error message MUST be displayed (e.g., "Authentication failed", "Connection refused")
-
-#### Scenario 8.3: Configure IMAP mailbox
-- GIVEN the admin settings
-- WHEN the admin enters IMAP host, port, encryption, username, password, and folder name
-- THEN the settings MUST be stored in `IAppConfig` under keys prefixed with `email_imap_`
-- AND the system MUST validate the connection immediately and display the result
-
-#### Scenario 8.4: Use Nextcloud Mail app as transport
-- GIVEN the Nextcloud Mail app is installed and the admin has configured a Mail account
-- WHEN the admin selects "Use Nextcloud Mail" in the email transport configuration
-- THEN outbound emails MUST be sent through the Mail app's SMTP infrastructure
-- AND the admin MUST select which Mail account to use from a dropdown
-
-#### Scenario 8.5: Configuration validation on save
-- GIVEN the admin enters email configuration
-- WHEN the admin clicks "Save"
-- THEN the system MUST validate that all required fields are filled (host, port, from-address for SMTP)
-- AND if validation fails, the specific missing fields MUST be highlighted with error messages
-
-### Requirement 9: Email OpenRegister schemas
+#### Scenario: Configure SMTP settings
+- **GIVEN** the Procest admin settings page (`Settings.vue` or dedicated email tab)
+- **WHEN** the admin enters SMTP host, port, encryption (none/STARTTLS/SSL), username, password, and from-address
+- **THEN** the settings MUST be stored in `IAppConfig` under keys prefixed with `email_smtp_`
+- **AND** the password MUST be stored encrypted using `ISecureRandom` or Nextcloud's credential store
+
+#### Scenario: Test SMTP connection
+- **GIVEN** SMTP settings are configured
+- **WHEN** the admin clicks "Send test email"
+- **THEN** the system MUST attempt to send a test email to the admin's email address
+- **AND** on success, a green "Connection successful" message MUST appear
+- **AND** on failure, the specific error message MUST be displayed (e.g., "Authentication failed", "Connection refused")
+
+#### Scenario: Configure IMAP mailbox
+- **GIVEN** the admin settings
+- **WHEN** the admin enters IMAP host, port, encryption, username, password, and folder name
+- **THEN** the settings MUST be stored in `IAppConfig` under keys prefixed with `email_imap_`
+- **AND** the system MUST validate the connection immediately and display the result
+
+#### Scenario: Use Nextcloud Mail app as transport
+- **GIVEN** the Nextcloud Mail app is installed and the admin has configured a Mail account
+- **WHEN** the admin selects "Use Nextcloud Mail" in the email transport configuration
+- **THEN** outbound emails MUST be sent through the Mail app's SMTP infrastructure
+- **AND** the admin MUST select which Mail account to use from a dropdown
+
+#### Scenario: Configuration validation on save
+- **GIVEN** the admin enters email configuration
+- **WHEN** the admin clicks "Save"
+- **THEN** the system MUST validate that all required fields are filled (host, port, from-address for SMTP)
+- **AND** if validation fails, the specific missing fields MUST be highlighted with error messages
+
+### Requirement: Email OpenRegister schemas
The system MUST define OpenRegister schemas for email templates, messages, and threads in the `procest_register.json` configuration.
-#### Scenario 9.1: emailTemplate schema definition
-- GIVEN the register configuration at `lib/Settings/procest_register.json`
-- WHEN the register is imported via `ConfigurationService::importFromApp()`
-- THEN an `emailTemplate` schema MUST be created with properties: name (string, required), subject (string, required), body (string/HTML, required), caseType (string/reference, required), variables (array of available variable names), version (integer, default 1), isActive (boolean, default true)
-
-#### Scenario 9.2: emailMessage schema definition
-- GIVEN the register configuration
-- WHEN the register is imported
-- THEN an `emailMessage` schema MUST be created with properties: messageId (string, RFC 2822 Message-ID), inReplyTo (string, optional), direction (enum: inbound/outbound), from (string), to (array of strings), cc (array of strings), bcc (array of strings), subject (string), body (string/HTML), case (string/reference to case), thread (string/reference to emailThread), pdfPath (string), pdfStatus (enum: pending/completed/failed), sentAt (datetime), templateId (string, optional reference to emailTemplate), templateVersion (integer, optional)
-
-#### Scenario 9.3: emailThread schema definition
-- GIVEN the register configuration
-- WHEN the register is imported
-- THEN an `emailThread` schema MUST be created with properties: subject (string), case (string/reference to case), messageCount (integer), firstMessageAt (datetime), lastMessageAt (datetime)
-
-#### Scenario 9.4: Schema auto-configuration
-- GIVEN the schemas are imported
-- WHEN `SettingsService::autoConfigureAfterImport()` runs
-- THEN the schema IDs for `emailTemplate`, `emailMessage`, and `emailThread` MUST be stored in `IAppConfig` under keys `email_template_schema`, `email_message_schema`, `email_thread_schema`
-- AND the object store MUST register these types via `registerObjectType()` during `initializeStores()`
-
-#### Scenario 9.5: Schema.org type annotations
-- GIVEN the email schemas in `procest_register.json`
-- WHEN the schemas are defined
-- THEN `emailTemplate` MUST include Schema.org annotation `schema:DigitalDocument`
-- AND `emailMessage` MUST include annotation `schema:EmailMessage`
-- AND `emailThread` MUST include annotation `schema:Conversation`
-
-### Requirement 10: Email tab in case detail view
+#### Scenario: emailTemplate schema definition
+- **GIVEN** the register configuration at `lib/Settings/procest_register.json`
+- **WHEN** the register is imported via `ConfigurationService::importFromApp()`
+- **THEN** an `emailTemplate` schema MUST be created with properties: name (string, required), subject (string, required), body (string/HTML, required), caseType (string/reference, required), variables (array of available variable names), version (integer, default 1), isActive (boolean, default true)
+
+#### Scenario: emailMessage schema definition
+- **GIVEN** the register configuration
+- **WHEN** the register is imported
+- **THEN** an `emailMessage` schema MUST be created with properties: messageId (string, RFC 2822 Message-ID), inReplyTo (string, optional), direction (enum: inbound/outbound), from (string), to (array of strings), cc (array of strings), bcc (array of strings), subject (string), body (string/HTML), case (string/reference to case), thread (string/reference to emailThread), pdfPath (string), pdfStatus (enum: pending/completed/failed), sentAt (datetime), templateId (string, optional reference to emailTemplate), templateVersion (integer, optional)
+
+#### Scenario: emailThread schema definition
+- **GIVEN** the register configuration
+- **WHEN** the register is imported
+- **THEN** an `emailThread` schema MUST be created with properties: subject (string), case (string/reference to case), messageCount (integer), firstMessageAt (datetime), lastMessageAt (datetime)
+
+#### Scenario: Schema auto-configuration
+- **GIVEN** the schemas are imported
+- **WHEN** `SettingsService::autoConfigureAfterImport()` runs
+- **THEN** the schema IDs for `emailTemplate`, `emailMessage`, and `emailThread` MUST be stored in `IAppConfig` under keys `email_template_schema`, `email_message_schema`, `email_thread_schema`
+- **AND** the object store MUST register these types via `registerObjectType()` during `initializeStores()`
+
+#### Scenario: Schema.org type annotations
+- **GIVEN** the email schemas in `procest_register.json`
+- **WHEN** the schemas are defined
+- **THEN** `emailTemplate` MUST include Schema.org annotation `schema:DigitalDocument`
+- **AND** `emailMessage` MUST include annotation `schema:EmailMessage`
+- **AND** `emailThread` MUST include annotation `schema:Conversation`
+
+### Requirement: Email tab in case detail view
The case detail view MUST include a dedicated email tab showing all email correspondence for the case.
-#### Scenario 10.1: Email tab displays message list
-- GIVEN a case with 8 emails across 3 threads
-- WHEN the case worker clicks the "Email" tab in the case detail view
-- THEN the tab MUST display all emails grouped by thread
-- AND each thread group MUST show the thread subject, message count, and date of last message
-- AND the most recent thread MUST appear at the top
-
-#### Scenario 10.2: Empty state for cases with no emails
-- GIVEN a case with no email correspondence
-- WHEN the case worker views the email tab
-- THEN an empty state MUST be shown with text "No email correspondence yet"
-- AND a "Send email" button MUST be prominently displayed
-
-#### Scenario 10.3: Email count badge in tab header
-- GIVEN a case with 5 emails
-- WHEN the case detail tabs render
-- THEN the Email tab MUST display a count badge showing "5"
-
-#### Scenario 10.4: Inline email view
-- GIVEN the email tab with message list
-- WHEN the case worker clicks on an email message
-- THEN the full email body MUST be displayed inline (expanding the message row)
-- AND the PDF download link MUST be available next to the message
-
-#### Scenario 10.5: Reply from email tab
-- GIVEN the email tab showing a received email
-- WHEN the case worker clicks "Reply" on a specific message
-- THEN the email compose dialog MUST open with the recipient pre-filled from the original sender
-- AND the subject MUST be prefixed with "RE:"
-- AND the original message body MUST be quoted below the compose area
-
-### Requirement 11: Accessibility and internationalization
+#### Scenario: Email tab displays message list
+- **GIVEN** a case with 8 emails across 3 threads
+- **WHEN** the case worker clicks the "Email" tab in the case detail view
+- **THEN** the tab MUST display all emails grouped by thread
+- **AND** each thread group MUST show the thread subject, message count, and date of last message
+- **AND** the most recent thread MUST appear at the top
+
+#### Scenario: Empty state for cases with no emails
+- **GIVEN** a case with no email correspondence
+- **WHEN** the case worker views the email tab
+- **THEN** an empty state MUST be shown with text "No email correspondence yet"
+- **AND** a "Send email" button MUST be prominently displayed
+
+#### Scenario: Email count badge in tab header
+- **GIVEN** a case with 5 emails
+- **WHEN** the case detail tabs render
+- **THEN** the Email tab MUST display a count badge showing "5"
+
+#### Scenario: Inline email view
+- **GIVEN** the email tab with message list
+- **WHEN** the case worker clicks on an email message
+- **THEN** the full email body MUST be displayed inline (expanding the message row)
+- **AND** the PDF download link MUST be available next to the message
+
+#### Scenario: Reply from email tab
+- **GIVEN** the email tab showing a received email
+- **WHEN** the case worker clicks "Reply" on a specific message
+- **THEN** the email compose dialog MUST open with the recipient pre-filled from the original sender
+- **AND** the subject MUST be prefixed with "RE:"
+- **AND** the original message body MUST be quoted below the compose area
+
+### Requirement: Accessibility and internationalization
The email integration MUST meet WCAG AA compliance and support both English and Dutch.
-#### Scenario 11.1: Keyboard navigation in email compose
-- GIVEN the email compose dialog is open
-- WHEN the user navigates using only the keyboard
-- THEN all form fields, buttons, and the template selector MUST be reachable via Tab key
-- AND the send button MUST be activatable via Enter key
-- AND Escape MUST close the dialog
-
-#### Scenario 11.2: Screen reader support for email list
-- GIVEN the email tab in case detail
-- WHEN a screen reader reads the email list
-- THEN each email MUST have an ARIA label including direction (sent/received), sender, date, and subject
-- AND thread groups MUST use ARIA role "group" with a label
-
-#### Scenario 11.3: Dutch language support
-- GIVEN a user with Dutch locale
-- WHEN viewing the email integration UI
-- THEN all labels, buttons, error messages, and empty states MUST be displayed in Dutch
-- AND default template names MUST be in Dutch (e.g., "Ontvangstbevestiging", "Informatieverzoek")
-
-### Requirement 12: Email audit trail integration
+#### Scenario: Keyboard navigation in email compose
+- **GIVEN** the email compose dialog is open
+- **WHEN** the user navigates using only the keyboard
+- **THEN** all form fields, buttons, and the template selector MUST be reachable via Tab key
+- **AND** the send button MUST be activatable via Enter key
+- **AND** Escape MUST close the dialog
+
+#### Scenario: Screen reader support for email list
+- **GIVEN** the email tab in case detail
+- **WHEN** a screen reader reads the email list
+- **THEN** each email MUST have an ARIA label including direction (sent/received), sender, date, and subject
+- **AND** thread groups MUST use ARIA role "group" with a label
+
+#### Scenario: Dutch language support
+- **GIVEN** a user with Dutch locale
+- **WHEN** viewing the email integration UI
+- **THEN** all labels, buttons, error messages, and empty states MUST be displayed in Dutch
+- **AND** default template names MUST be in Dutch (e.g., "Ontvangstbevestiging", "Informatieverzoek")
+
+### Requirement: Email audit trail integration
All email events MUST be recorded in the case activity timeline for compliance and audit purposes.
-#### Scenario 12.1: Sent email appears in activity timeline
-- GIVEN a case with the activity timeline component (`ActivityTimeline.vue`)
-- WHEN an email is sent from the case
-- THEN the activity array MUST include an entry with type `email_sent`, the template name (if used), recipient list, and timestamp
-- AND the timeline MUST display an email icon for email events
-
-#### Scenario 12.2: Received email appears in activity timeline
-- GIVEN an incoming email is linked to a case (auto or manual)
-- WHEN the case detail loads
-- THEN the activity array MUST include an entry with type `email_received`, sender email, subject line, and timestamp
-
-#### Scenario 12.3: Email events in ZGW audit trail
-- GIVEN ZGW mapping is configured for the case type
-- WHEN an email event occurs
-- THEN the event MUST be mappable to a ZGW AuditTrail entry via `ZgwMappingService`
-- AND the informatieobject (PDF document) MUST be linkable via `zaakInformatieobject`
+#### Scenario: Sent email appears in activity timeline
+- **GIVEN** a case with the activity timeline component (`ActivityTimeline.vue`)
+- **WHEN** an email is sent from the case
+- **THEN** the activity array MUST include an entry with type `email_sent`, the template name (if used), recipient list, and timestamp
+- **AND** the timeline MUST display an email icon for email events
+
+#### Scenario: Received email appears in activity timeline
+- **GIVEN** an incoming email is linked to a case (auto or manual)
+- **WHEN** the case detail loads
+- **THEN** the activity array MUST include an entry with type `email_received`, sender email, subject line, and timestamp
+
+#### Scenario: Email events in ZGW audit trail
+- **GIVEN** ZGW mapping is configured for the case type
+- **WHEN** an email event occurs
+- **THEN** the event MUST be mappable to a ZGW AuditTrail entry via `ZgwMappingService`
+- **AND** the informatieobject (PDF document) MUST be linkable via `zaakInformatieobject`
## Dependencies
- Nextcloud Mail app (optional, for using existing Mail accounts as transport)
diff --git a/openspec/changes/case-management/specs/case-management/spec.md b/openspec/changes/case-management/specs/case-management/spec.md
deleted file mode 100644
index 0258e810..00000000
--- a/openspec/changes/case-management/specs/case-management/spec.md
+++ /dev/null
@@ -1,145 +0,0 @@
----
-status: implemented
----
-# case-management Specification
-
-## Purpose
-Define the foundational case lifecycle on top of OpenRegister: creating, reading, updating, deleting, listing, searching, validating, and presenting case objects with their custom properties and required documents. This spec documents the MVP surface implemented by this change set; suspension, sub-cases, and confidentiality enforcement remain out of scope.
-
-## Context
-A `case` (zaak) is the primary domain object in Procest. Cases are stored as OpenRegister objects and rendered through `CaseList.vue` and `CaseDetail.vue` using the shared `createObjectStore` pattern. The list view consumes `_filters` and `_search` query parameters, while detail panels (`CustomPropertiesPanel.vue`, `DocumentChecklist.vue`) read property definitions and document type requirements off the linked case type. Validation rules live in `caseValidation.js`.
-
-## Requirements
-
-### REQ-CM-LIST-01: Case List Filters
-The case list MUST expose filters for handler, priority, and overdue status.
-
-#### Scenario CM-LIST-01-1: Filter by handler
-- GIVEN the case list contains cases assigned to multiple handlers
-- WHEN the user selects a handler from the handler filter dropdown
-- THEN the list MUST refresh with `_filters[handler]` applied via the object store
-- AND only cases assigned to the selected handler MUST be visible
-
-#### Scenario CM-LIST-01-2: Filter by priority
-- WHEN the user selects a priority value (e.g., "high")
-- THEN the list MUST refresh with `_filters[priority]` applied
-- AND only cases matching that priority MUST be visible
-
-#### Scenario CM-LIST-01-3: Filter overdue cases
-- WHEN the user activates the "overdue" toggle
-- THEN the list MUST be filtered to cases whose deadline is in the past and whose status is not final
-
-#### Scenario CM-LIST-01-4: Combined filters
-- WHEN the user combines case type, status, handler, priority, and overdue filters
-- THEN all filters MUST be applied as an AND condition and reflected in the URL query state
-
-### REQ-CM-LIST-02: Case Search
-The case list MUST support keyword search across title, description, and identifier.
-
-#### Scenario CM-LIST-02-1: Search by keyword
-- GIVEN a case "Omgevingsvergunning verbouwing Kerkstraat 12" with identifier "ZAAK-2026-000123"
-- WHEN the user types "Kerkstraat" into the search field
-- THEN the list MUST refresh with `_search=Kerkstraat`
-- AND the matching case MUST appear in the results
-
-#### Scenario CM-LIST-02-2: Search by identifier
-- WHEN the user types a full or partial case identifier
-- THEN matching cases MUST appear in the results regardless of title content
-
-#### Scenario CM-LIST-02-3: Empty search
-- WHEN the search field is cleared
-- THEN the `_search` parameter MUST be removed and the full filtered list MUST return
-
-### REQ-CM-PROP-01: Custom Properties Panel
-The case detail view MUST render a panel showing the case's custom properties as defined by its case type.
-
-#### Scenario CM-PROP-01-1: Render configured properties
-- GIVEN a case whose case type defines properties `["aanvraagdatum", "locatie", "bouwsom"]`
-- WHEN the user opens the case detail view
-- THEN the custom properties panel MUST render each property with its label and current value
-- AND properties not defined on the case type MUST NOT appear
-
-#### Scenario CM-PROP-01-2: Edit a property value
-- WHEN the user edits a property value through the panel and saves
-- THEN the new value MUST be persisted on the case object
-- AND the panel MUST reflect the updated value without a full page reload
-
-#### Scenario CM-PROP-01-3: Property panel with no definitions
-- GIVEN a case whose case type defines no custom properties
-- THEN the panel MUST render an empty state ("Geen aanvullende kenmerken") and remain hidden from primary actions
-
-### REQ-CM-DOC-01: Required Documents Checklist
-The case detail view MUST render a checklist showing required document types and their completion status.
-
-#### Scenario CM-DOC-01-1: Render required documents
-- GIVEN a case whose case type defines document types with `required=true`
-- WHEN the user opens the case detail view
-- THEN the checklist MUST list each required document type with a checkmark when at least one matching case document is present
-
-#### Scenario CM-DOC-01-2: Missing documents flagged
-- GIVEN a required document type with no matching case document
-- THEN the checklist MUST render that row as incomplete with an explicit "Ontbreekt" label
-
-#### Scenario CM-DOC-01-3: Optional documents excluded
-- GIVEN a case type that defines optional document types in addition to required ones
-- THEN only the required types MUST appear in the checklist
-
-### REQ-CM-VAL-01: Strengthened Case Validation
-Case creation and update MUST surface explicit validation errors for case type validity, missing title, and missing case type.
-
-#### Scenario CM-VAL-01-1: Case type not yet valid
-- GIVEN a case type with `validFrom` in the future
-- WHEN the user attempts to create a case using it
-- THEN `caseValidation.js` MUST return an error identifying the case type and its `validFrom` date
-- AND the form MUST display this error inline next to the case type field
-
-#### Scenario CM-VAL-01-2: Case type expired
-- GIVEN a case type with `validTo` in the past
-- WHEN the user attempts to create a case using it
-- THEN validation MUST fail with an explicit "vervallen" error referencing the expiration date
-
-#### Scenario CM-VAL-01-3: Missing required fields
-- GIVEN a case create form without title or case type
-- WHEN the user submits
-- THEN each missing required field MUST be highlighted with a Dutch-language error message
-- AND the save action MUST be blocked until errors are resolved
-
-### REQ-CM-CRUD-01: Case CRUD Foundation
-The system MUST support create, read, update, and delete operations on case objects through the shared object store.
-
-#### Scenario CM-CRUD-01-1: Create case
-- GIVEN a valid case type and title
-- WHEN the user submits the create form
-- THEN a new case object MUST be persisted via `createObjectStore('case').save(...)`
-- AND the user MUST be redirected to the new case's detail view
-
-#### Scenario CM-CRUD-01-2: Update case
-- WHEN the user edits a case field (e.g., description, priority, handler) and saves
-- THEN the update MUST be persisted and the detail view MUST reflect the new value
-
-#### Scenario CM-CRUD-01-3: Delete case in initial status
-- GIVEN a case in its initial status with no dependent links
-- WHEN the user confirms deletion
-- THEN the case object MUST be deleted via the object store
-- AND the user MUST be returned to the case list
-
-### REQ-CM-INT-01: Custom Properties and Document Checklist Integration
-The case detail view MUST integrate the custom properties panel and the document checklist alongside existing panels.
-
-#### Scenario CM-INT-01-1: Panels visible on detail view
-- GIVEN a case with custom properties and required documents
-- WHEN the user opens the detail view
-- THEN both `CustomPropertiesPanel.vue` and `DocumentChecklist.vue` MUST be visible
-- AND they MUST coexist with the status timeline, deadline, activity, and tasks panels without layout overlap
-
-## Non-Requirements
-- Case suspension (out of scope, deferred to V1)
-- Sub-cases / parent-child case relations (deferred to V1)
-- Confidentiality (`vertrouwelijkheidaanduiding`) enforcement at view-time (deferred to V1)
-- Status blocking by missing properties or documents (deferred to V1)
-
-## Dependencies
-- OpenRegister `case` schema and shared `createObjectStore`
-- `CaseList.vue`, `CaseDetail.vue`, `CustomPropertiesPanel.vue`, `DocumentChecklist.vue`
-- `caseValidation.js` validation utility
-- `case-types` capability for property definitions and document type definitions
diff --git a/openspec/changes/case-sharing-collaboration/specs/case-sharing-collaboration/spec.md b/openspec/changes/case-sharing-collaboration/specs/case-sharing-collaboration/spec.md
deleted file mode 100644
index 4ce89652..00000000
--- a/openspec/changes/case-sharing-collaboration/specs/case-sharing-collaboration/spec.md
+++ /dev/null
@@ -1,292 +0,0 @@
----
-status: implemented
----
-# case-sharing-collaboration Specification
-
-## Purpose
-Share case access with external parties (ketenpartners) for inter-organizational collaboration on cases. Supports both token-based access for ad-hoc sharing and account-based access for recurring partners, with scoped permissions controlling what shared parties can view and do.
-
-## Context
-Dutch government case processing frequently requires collaboration between organizations: housing corporations reviewing permit applications, police providing input on event permits, healthcare providers contributing to youth care cases. Currently this happens via email with document attachments, losing audit trail and version control. This spec enables structured case sharing with access controls.
-
-Procest already integrates with Nextcloud's sharing infrastructure (`OCP\Share\IManager`) for file sharing and uses OpenRegister RBAC for permission enforcement. The `ZgwAuthMiddleware` demonstrates external API authentication patterns. This spec extends these foundations to enable case-level sharing with granular permission scoping and partner organization management.
-
-## Requirements
-
-### Requirement: Share case with external party via secure token link
-The system MUST support sharing a case with an external party using a cryptographically secure, time-limited token URL.
-
-#### Scenario: Create share link with configurable permissions
-- GIVEN a case worker on case "ZAAK-2026-001234"
-- WHEN they click "Delen" and select "Link delen"
-- THEN the system MUST generate a unique, cryptographically secure token URL (min 128 bits entropy)
-- AND the case worker MUST be able to set: expiration date, permission level (bekijken / bekijken + reageren / bekijken + bijdragen), and optional password
-- AND the share MUST be logged in the case audit trail with: creator, timestamp, permission level, and expiration
-
-#### Scenario: Access shared case via token with view permission
-- GIVEN a valid share token for case "ZAAK-2026-001234" with "bekijken" permission
-- WHEN the external party opens the token URL in a browser
-- THEN they MUST see a public case view with: case title, current status, milestone progress, and selected documents
-- AND they MUST NOT see internal notes, assigned case worker names, risk scores, or other restricted fields
-- AND they MUST NOT see any other cases or system navigation
-
-#### Scenario: Access shared case via token with comment permission
-- GIVEN a valid share token with "bekijken + reageren" permission
-- WHEN the external party accesses the case
-- THEN they MUST be able to view case details and add comments
-- BUT they MUST NOT be able to upload documents, change case status, or modify any case data
-- AND comments MUST be tagged with an external party identifier (name or organization, entered on first access)
-
-#### Scenario: Expired token shows Dutch-language error
-- GIVEN a share token that has passed its expiration date
-- WHEN the external party attempts to access the case
-- THEN the system MUST display "Deze link is verlopen. Neem contact op met de behandelaar." and deny access
-- AND the expired access attempt MUST be logged
-
-#### Scenario: Password-protected share link
-- GIVEN a share token with password protection enabled
-- WHEN the external party opens the token URL
-- THEN a password prompt MUST be displayed before granting access
-- AND after 5 failed password attempts, the token MUST be temporarily locked for 15 minutes
-
-### Requirement: Share case with registered partner organization
-The system MUST support sharing cases with registered partner organizations (ketenpartners) who have persistent accounts.
-
-#### Scenario: Share with registered ketenpartner
-- GIVEN a registered ketenpartner "Woningbouwvereniging Utrecht" with a partner account in the system
-- WHEN the case worker shares case "ZAAK-2026-001234" with this partner
-- THEN the partner's authorized users MUST see the case in their "Gedeelde zaken" view
-- AND the share MUST be scoped to the configured permission level
-- AND a notification MUST be sent to the partner organization's primary contact
-
-#### Scenario: Partner organization user management
-- GIVEN a registered ketenpartner "Woningbouwvereniging Utrecht"
-- WHEN the partner admin manages their organization's users in the partner portal
-- THEN they MUST be able to add/remove users who can access shared cases
-- AND each user MUST authenticate via their own credentials (Nextcloud account, eHerkenning, or local account)
-- AND user changes MUST take effect immediately (no pending approval)
-
-#### Scenario: Partner sees only shared cases
-- GIVEN "Woningbouwvereniging Utrecht" has been shared 3 cases from municipality A
-- WHEN a partner user logs in
-- THEN they MUST see exactly those 3 cases in their "Gedeelde zaken" view
-- AND they MUST NOT see any other cases, navigation items, or system configuration
-- AND the view MUST show: case title, status, shared date, permission level, and municipality name
-
-#### Scenario: Register new partner organization
-- GIVEN a municipality admin wants to add a new ketenpartner
-- WHEN they navigate to Settings > Partners and click "Partner toevoegen"
-- THEN they MUST provide: organization name, OIN (if applicable), contact email, and default permission level
-- AND the system MUST create an OpenRegister object with the partner organization data
-- AND a partner admin account MUST be provisioned with a Nextcloud user in the `ketenpartner_{slug}` group
-
-### Requirement: Granular permission levels with field-level control
-Shared access MUST be controllable with granular permission levels and field-level restrictions.
-
-#### Scenario: View-only sharing excludes internal fields
-- GIVEN a case shared with permission level "bekijken"
-- WHEN the external party views the case
-- THEN they MUST see: case title, identifier, current status, milestone progress, and public documents
-- AND they MUST NOT see: internal notes (`interneAantekening`), risk scores (`risicoScore`), assigned case worker, cost estimates, or case history details
-
-#### Scenario: View + contribute sharing allows document upload
-- GIVEN a case shared with permission level "bekijken + bijdragen"
-- WHEN the external party accesses the case
-- THEN they MUST be able to upload documents (max 50 MB per file, PDF/DOC/DOCX/JPG/PNG) and add comments
-- AND uploaded documents MUST be tagged as "extern aangeleverd" with the uploader's identity
-- AND they MUST NOT be able to change case status, zaaktype, assigned worker, or delete existing documents
-
-#### Scenario: Field-level share restrictions via configuration
-- GIVEN a share configuration that includes field exclusions: `["interneAantekening", "risicoScore", "kosteninschatting"]`
-- WHEN the external party views the case via API or UI
-- THEN the excluded fields MUST NOT appear in the case view or API response (not even as empty/null)
-- AND the field exclusion MUST be enforced at the API layer before serialization
-
-#### Scenario: Permission level definitions are configurable per tenant
-- GIVEN a municipality admin accesses Settings > Deelrechten
-- WHEN they define permission levels
-- THEN they MUST be able to create custom permission levels with specific field inclusions/exclusions
-- AND default permission levels ("bekijken", "bekijken + reageren", "bekijken + bijdragen") MUST be pre-configured
-
-### Requirement: Share lifecycle management
-Case workers MUST be able to view, modify, and revoke active shares on their cases.
-
-#### Scenario: View active shares on case detail
-- GIVEN a case with 3 active shares (2 token-based, 1 partner account)
-- WHEN the case worker opens the "Delen" tab in the case detail sidebar
-- THEN all active shares MUST be listed with: type (link/partner), recipient/label, permission level, creation date, expiration date, last accessed date
-- AND each share MUST have an "Intrekken" (revoke) button and an "Aanpassen" (modify) button
-
-#### Scenario: Revoke share immediately blocks access
-- GIVEN an active share on a case
-- WHEN the case worker clicks "Intrekken" and confirms
-- THEN the external party MUST immediately lose access (next page load shows "Toegang ingetrokken")
-- AND the revocation MUST be logged in the audit trail with: revoker, timestamp, and share details
-- AND any active sessions using the revoked share MUST be invalidated
-
-#### Scenario: Modify share permission level
-- GIVEN a token-based share with "bekijken + bijdragen" permission
-- WHEN the case worker changes the permission to "bekijken" only
-- THEN the external party's next access MUST reflect the reduced permissions
-- AND any pending uploads from the external party MUST still be processed (no data loss)
-- AND the permission change MUST be logged in the audit trail
-
-#### Scenario: Bulk share management
-- GIVEN a case worker handles 20 cases shared with "Politie Utrecht"
-- WHEN the case worker navigates to the partner management view
-- THEN they MUST see all cases shared with "Politie Utrecht" in a single list
-- AND they MUST be able to revoke all shares for that partner at once or modify permissions in bulk
-
-### Requirement: External access activity tracking
-All actions by external parties on shared cases MUST be tracked in the case audit trail.
-
-#### Scenario: External party views case
-- GIVEN a shared case accessed by an external party
-- WHEN they view the case
-- THEN the audit trail MUST record: "Zaak bekeken door extern: Woningbouwvereniging Utrecht (J. de Vries)" with timestamp and IP address
-- AND the access MUST be recorded even if the party only views and takes no action
-
-#### Scenario: External party uploads document
-- GIVEN a case shared with "bekijken + bijdragen" permission
-- WHEN the external party uploads a document "brandveiligheidsadvies.pdf"
-- THEN the document MUST be stored in the case's Nextcloud folder under a subfolder "Extern aangeleverd"
-- AND the document MUST be tagged with: uploader identity, upload timestamp, and source organization
-- AND the audit trail MUST record: "Document geupload door extern: Woningbouwvereniging Utrecht - brandveiligheidsadvies.pdf"
-
-#### Scenario: External party adds comment
-- GIVEN a case shared with "bekijken + reageren" permission
-- WHEN the external party adds a comment "Brandveiligheid voldoet aan eisen"
-- THEN the comment MUST be stored with: author (external party identity), timestamp, and "extern" tag
-- AND the comment MUST be visible to case workers in the activity timeline
-- AND the case worker MUST receive a notification about the new external comment
-
-### Requirement: Case transfer between organizations
-The system MUST support transferring case ownership from one organization to another.
-
-#### Scenario: Initiate case transfer
-- GIVEN case "ZAAK-2026-001234" is owned by municipality A
-- AND municipality B's organization is registered as a ketenpartner
-- WHEN the case worker initiates a transfer to municipality B
-- THEN the system MUST create a transfer request with: source org, target org, case reference, reason, and requested transfer date
-- AND the target organization's admin MUST receive a notification to accept or reject the transfer
-
-#### Scenario: Accept case transfer
-- GIVEN a pending transfer request for case "ZAAK-2026-001234"
-- WHEN the target organization's admin accepts the transfer
-- THEN the case MUST be copied to the target organization's register
-- AND all documents, status history, and milestone records MUST be included
-- AND the source organization MUST retain a read-only archive copy
-- AND both organizations' audit trails MUST record the transfer
-
-#### Scenario: Reject case transfer with reason
-- GIVEN a pending transfer request
-- WHEN the target organization's admin rejects the transfer with reason "Niet bevoegd"
-- THEN the source case worker MUST be notified with the rejection reason
-- AND the case MUST remain with the source organization unchanged
-
-### Requirement: Public case status page for citizens
-Citizens MUST be able to check their case progress via a public URL without authentication.
-
-#### Scenario: Citizen receives case status link
-- GIVEN a citizen submitted a permit application creating case "ZAAK-2026-001234"
-- WHEN the case worker sends a status notification
-- THEN the notification MUST include a public status URL (e.g., `/publiek/zaak/{token}`)
-- AND the token MUST be unique, non-guessable, and linked to the specific case
-
-#### Scenario: Citizen views case progress
-- GIVEN a citizen opens the public status URL
-- THEN they MUST see: case title, current milestone progress (visual step indicator), current status label, and expected completion date
-- AND they MUST NOT see: case worker details, internal notes, documents, or any actionable controls
-- AND the page MUST comply with WCAG 2.1 AA and use NL Design System tokens
-
-#### Scenario: Public status page respects case sensitivity
-- GIVEN a case is marked as "vertrouwelijk" (confidential)
-- WHEN the system generates a status notification
-- THEN the public status URL MUST NOT be generated
-- AND the citizen MUST be informed via alternative channels (letter, phone)
-
-### Requirement: Notification system for share events
-Case workers and external parties MUST be notified about share-related events.
-
-#### Scenario: Case worker notified of external activity
-- GIVEN a case shared with a ketenpartner
-- WHEN the ketenpartner uploads a document or adds a comment
-- THEN the case worker MUST receive a Nextcloud notification: "Extern document ontvangen op ZAAK-2026-001234 van Woningbouwvereniging Utrecht"
-- AND the notification MUST link to the case detail view
-
-#### Scenario: External party notified of case updates
-- GIVEN a case shared with a ketenpartner with "bekijken" permission
-- WHEN the case status changes
-- THEN the ketenpartner's primary contact MUST receive an email notification: "Status update voor ZAAK-2026-001234: Besluit genomen"
-- AND the email MUST include a link to the shared case view (not the internal case detail)
-
-#### Scenario: Share expiration reminder
-- GIVEN a token-based share expiring in 3 days
-- WHEN the daily share maintenance job runs
-- THEN the case worker MUST receive a notification: "Deellink voor ZAAK-2026-001234 verloopt over 3 dagen"
-- AND they MUST be able to extend the expiration directly from the notification
-
-### Requirement: Data minimization for shared access
-Shared case views MUST apply data minimization principles per AVG/GDPR.
-
-#### Scenario: Personal data excluded from partner view
-- GIVEN a case about a building permit that includes the applicant's BSN, address, and phone number
-- WHEN shared with a ketenpartner for technical review
-- THEN the applicant's BSN MUST be masked (showing only last 4 digits)
-- AND personal contact details MUST be excluded unless the permission level explicitly includes them
-- AND the data minimization rules MUST be configurable per permission level
-
-#### Scenario: Document metadata stripped for external access
-- GIVEN a case document containing metadata (author, revision history, comments)
-- WHEN an external party downloads the document via a shared case view
-- THEN internal metadata MUST be stripped from the downloaded copy
-- AND the original document in Nextcloud MUST remain unchanged
-
-#### Scenario: Audit report for shared personal data
-- GIVEN a case with personal data was shared with 3 ketenpartners over 6 months
-- WHEN a privacy officer requests a data sharing report
-- THEN the system MUST generate: which personal data fields were shared, with whom, when, for how long, and under which legal basis
-
-## Non-Requirements
-- This spec does NOT cover real-time collaborative editing (simultaneous case editing by multiple parties)
-- This spec does NOT cover federated identity management between municipalities
-- This spec does NOT cover automated case routing between organizations based on jurisdiction
-
-## Dependencies
-- OpenRegister RBAC for permission enforcement
-- Nextcloud share infrastructure (`OCP\Share\IManager`) for token generation and expiration management
-- Nextcloud notification system (`OCP\Notification\IManager`) for share event notifications
-- Audit trail system (OpenRegister audit trails plugin) for tracking external access
-- NL Design System tokens for public case status page styling
-- n8n for email notifications to external parties
-- Partner organization registry (new OpenRegister schema: `partnerOrganization`)
-- CaseDetail.vue sidebar for "Delen" tab integration
-
----
-
-### Current Implementation Status
-
-**Not yet implemented.** No sharing, token-based access, or ketenpartner collaboration functionality exists in the Procest codebase. There are no share-related schemas, controllers, services, or Vue components.
-
-**Foundation available:**
-- Nextcloud's share infrastructure (`OCP\Share\IManager`) provides token-based sharing with expiration, password protection, and permission levels -- could be leveraged for case sharing.
-- OpenRegister RBAC provides the permission enforcement layer.
-- The audit trail plugin in the object store (`auditTrailsPlugin` in `src/store/modules/object.js`) could track external access events.
-- ZGW authentication middleware (`lib/Middleware/ZgwAuthMiddleware.php`) demonstrates external API authentication patterns that could be adapted for partner access.
-- The `CaseDetail.vue` sidebar already supports tabs (via `sidebarProps`) where a "Delen" tab could be added.
-- The `role` schema in OpenRegister could represent external party roles on shared cases.
-
-**Partial implementations:** None.
-
-### Standards & References
-
-- **Nextcloud Sharing API**: Token-based sharing with expiration, passwords, and permission scopes. Procest can extend Nextcloud's `IShare` interface for case-level sharing.
-- **eHerkenning**: Dutch government-to-business authentication standard for partner organization users. Level 3 (substantieel) recommended for case access.
-- **DigiD**: Dutch citizen authentication for citizen-facing case access (out of scope for this spec but relevant for public status page).
-- **AVG/GDPR**: Data sharing with external parties requires purpose limitation, data minimization, and processing agreements. Article 28 (processor agreements) applies to ketenpartner data access.
-- **BIO (Baseline Informatiebeveiliging Overheid)**: Security requirements for government data sharing, including access logging, encryption in transit, and data classification.
-- **Common Ground**: Federated data access patterns for inter-organizational collaboration. The "notificeren" and "autoriseren" components are relevant.
-- **ZGW Autorisaties API (VNG)**: Authorization scopes for external system access to case data. Could model permission levels as ZGW autorisatie objects.
-- **ArkCase**: Uses `AcmParticipant` model for access control on cases -- participants can be internal users, groups, or external contacts. Similar pattern to Procest's ketenpartner concept.
-- **Dimpact ZAC**: Shares cases between groups via group-based assignment. Does not support external organization sharing -- an opportunity for Procest differentiation.
-- **Ketensamenwerking**: Dutch government term for chain collaboration between public organizations. VNG has published guidelines for secure ketendata exchange.
diff --git a/openspec/changes/case-types/specs/case-types/spec.md b/openspec/changes/case-types/specs/case-types/spec.md
deleted file mode 100644
index 0378f804..00000000
--- a/openspec/changes/case-types/specs/case-types/spec.md
+++ /dev/null
@@ -1,125 +0,0 @@
----
-status: implemented
----
-# case-types Specification
-
-## Purpose
-Define the V1 admin surface for configuring case types (zaaktypen) in Procest: result types, role types, property definitions, document types, the tab layout that hosts them, and the publish-time validation that ensures a case type is usable before it goes live. Status type, deadline, and confidentiality configuration are already shipped via the earlier `zaaktype-configuratie` change; this spec layers the remaining V1 tabs on top.
-
-## Context
-Case types drive every downstream capability: statuses, deadlines, custom properties, required documents, roles, and result/outcome handling. The admin UI is hosted by `CaseTypeDetail.vue`, which composes per-concern tab components. This change introduces the missing V1 tabs (`ResultTypesTab.vue`, `RoleTypesTab.vue`, `PropertiesTab.vue`, `DocumentTypesTab.vue`) and strengthens publish-time validation.
-
-## Requirements
-
-### REQ-CT-RESULT-01: Result Type Management Tab
-The case type admin UI MUST provide a tab for managing result types with archival rules.
-
-#### Scenario CT-RESULT-01-1: List result types
-- GIVEN a case type with one or more configured result types
-- WHEN the admin opens the "Resultaattypen" tab on `CaseTypeDetail.vue`
-- THEN each result type MUST be listed with: name, description, archival classification, and retention period
-
-#### Scenario CT-RESULT-01-2: Create result type
-- WHEN the admin clicks "Resultaattype toevoegen" and submits the form with name, description, and archival rule
-- THEN a new result type MUST be persisted on the case type
-- AND the tab MUST refresh to show the new entry
-
-#### Scenario CT-RESULT-01-3: Edit and delete result type
-- WHEN the admin edits or deletes an existing result type
-- THEN the change MUST be persisted via the object store
-- AND deletion MUST be blocked with a Dutch-language error if any case currently references the result type
-
-### REQ-CT-ROLE-01: Role Type Management Tab
-The case type admin UI MUST provide a tab for managing role types referencing the generic role registry.
-
-#### Scenario CT-ROLE-01-1: List role types
-- WHEN the admin opens the "Roltypen" tab
-- THEN each configured role type MUST be listed with: name, description, generic role (rolomschrijvinggeneriek), and required flag
-
-#### Scenario CT-ROLE-01-2: Create role type with generic role selector
-- WHEN the admin adds a new role type and picks a generic role from the dropdown ("Initiator", "Belanghebbende", "Behandelaar", etc.)
-- THEN the role type MUST be persisted with both the local label and the generic role mapping
-
-#### Scenario CT-ROLE-01-3: Required role validation
-- GIVEN a role type marked as required
-- THEN cases of this case type MUST surface a warning when the required role is not filled
-- AND the role types tab MUST clearly indicate which roles are required
-
-### REQ-CT-PROP-01: Property Definition Management Tab
-The case type admin UI MUST provide a tab for managing property definitions used by the custom properties panel.
-
-#### Scenario CT-PROP-01-1: List property definitions
-- WHEN the admin opens the "Kenmerken" tab
-- THEN each property definition MUST be listed with: key, label, type, required flag, and default value
-
-#### Scenario CT-PROP-01-2: Create property definition
-- WHEN the admin adds a new property definition with key, label, type (text/number/date/select), required flag, and optional default
-- THEN the definition MUST be persisted on the case type
-- AND it MUST become available in the `CustomPropertiesPanel.vue` on cases of this type
-
-#### Scenario CT-PROP-01-3: Edit and delete property definition
-- WHEN the admin edits or deletes a property definition
-- THEN the change MUST be persisted
-- AND existing case property values MUST remain intact; deletion only removes the definition, not historical data
-
-### REQ-CT-DOC-01: Document Type Management Tab
-The case type admin UI MUST provide a tab for managing document types with a direction (incoming/outgoing) and a required flag.
-
-#### Scenario CT-DOC-01-1: List document types
-- WHEN the admin opens the "Documenttypen" tab
-- THEN each document type MUST be listed with: name, description, direction (inkomend/uitgaand/intern), and required flag
-
-#### Scenario CT-DOC-01-2: Create document type
-- WHEN the admin adds a new document type with name, direction, and required flag
-- THEN the document type MUST be persisted on the case type
-- AND required document types MUST appear in `DocumentChecklist.vue` on cases of this type
-
-#### Scenario CT-DOC-01-3: Edit and delete document type
-- WHEN the admin edits or deletes a document type
-- THEN the change MUST be persisted
-- AND existing case documents MUST remain intact; deletion only removes the definition
-
-### REQ-CT-TABS-01: V1 Tab Layout
-`CaseTypeDetail.vue` MUST present all V1 admin concerns through a stable, navigable tab layout.
-
-#### Scenario CT-TABS-01-1: Tab order and labels
-- WHEN the admin opens a case type detail view
-- THEN the tabs MUST be visible in this order: "Algemeen", "Statussen", "Resultaattypen", "Roltypen", "Kenmerken", "Documenttypen", "Besluiten", "Doorlooptijd"
-- AND each tab MUST be labeled in Dutch
-
-#### Scenario CT-TABS-01-2: Deep link to tab
-- WHEN the admin navigates with a URL fragment selecting a tab (e.g., `#documenttypen`)
-- THEN the corresponding tab MUST be active on initial render
-
-### REQ-CT-PUBLISH-01: Publish Validation
-The case type publish action MUST be blocked when minimum configuration requirements are not met.
-
-#### Scenario CT-PUBLISH-01-1: Missing status types
-- GIVEN a case type with no status types configured
-- WHEN the admin clicks "Publiceren"
-- THEN publish MUST be blocked with the error: "Een zaaktype moet minimaal een initiele en een eind-status hebben"
-
-#### Scenario CT-PUBLISH-01-2: Missing initial or final status
-- GIVEN a case type with status types but none marked initial or none marked final
-- THEN publish MUST be blocked with a Dutch-language error identifying the missing flag
-
-#### Scenario CT-PUBLISH-01-3: Validity period
-- GIVEN a case type with `validFrom > validTo`
-- THEN publish MUST be blocked with the error: "Geldig-vanaf datum moet voor geldig-tot datum liggen"
-
-#### Scenario CT-PUBLISH-01-4: Successful publish
-- GIVEN a case type with at least one initial status, one final status, a valid validity period, and a name
-- WHEN the admin clicks "Publiceren"
-- THEN the case type's `status` MUST transition from `draft` to `published`
-- AND it MUST become selectable on the case creation form
-
-## Non-Requirements
-- Versioning of case types across published versions (deferred)
-- Cross-case-type cloning / templating (deferred)
-- Audit history of case-type configuration changes beyond what OpenRegister provides natively
-
-## Dependencies
-- OpenRegister `caseType` schema and related schemas: `resultType`, `roleType`, `propertyDefinition`, `documentType`
-- `CaseTypeDetail.vue` and the per-concern tab components
-- `case-management` capability (consumes property definitions and document types)
-- Existing `zaaktype-configuratie` change (status types, deadlines, confidentiality)
diff --git a/openspec/changes/complaint-management/specs/complaint-management/spec.md b/openspec/changes/complaint-management/specs/complaint-management/spec.md
index b7cee790..c15c564c 100644
--- a/openspec/changes/complaint-management/specs/complaint-management/spec.md
+++ b/openspec/changes/complaint-management/specs/complaint-management/spec.md
@@ -11,15 +11,14 @@ In Dutch municipal practice, the Algemene wet bestuursrecht (Awb) chapter 9 mand
Procest's case management infrastructure (cases, tasks, statuses, roles, results) can model complaints as a specialized case type with Awb-mandated deadlines. The `caseType` schema supports `processingDeadline`, and the status type system can define the complaint lifecycle. ArkCase implements complaints as a separate entity with its own plugin, pipeline, and close/approval workflow -- Procest can achieve similar functionality through configuration plus targeted new components for Awb-specific features.
-## Requirements
-
+## ADDED Requirements
### Requirement: Complaints MUST be first-class entities with dedicated schema
The system SHALL treat complaints as first-class entities with their own OpenRegister schema and lifecycle, distinct from a regular zaak but sharing the case infrastructure.
#### Scenario: Register a new complaint via intake form
-- GIVEN the Procest complaints module is enabled
-- WHEN a case worker registers a complaint received from a citizen
-- THEN a complaint object MUST be created in OpenRegister with:
+- **GIVEN** the Procest complaints module is enabled
+- **WHEN** a case worker registers a complaint received from a citizen
+- **THEN** a complaint object MUST be created in OpenRegister with:
- `klachtnummer`: auto-generated (format: `KL-{year}-{sequence}`, e.g., `KL-2026-0042`)
- `klager`: reference to the person filing the complaint (name, email, phone, BSN if known)
- `onderwerp`: subject of the complaint (short title)
@@ -34,270 +33,270 @@ The system SHALL treat complaints as first-class entities with their own OpenReg
- `prioriteit`: priority level (`laag`, `normaal`, `hoog`, `urgent`)
#### Scenario: Complaint numbering is sequential per year
-- GIVEN 41 complaints have been registered in 2026
-- WHEN a new complaint is created on 2026-03-20
-- THEN the complaint number MUST be `KL-2026-0042`
-- AND the sequence MUST reset to 0001 on January 1, 2027
+- **GIVEN** 41 complaints have been registered in 2026
+- **WHEN** a new complaint is created on 2026-03-20
+- **THEN** the complaint number MUST be `KL-2026-0042`
+- **AND** the sequence MUST reset to 0001 on January 1, 2027
#### Scenario: Complaint intake from multiple channels
-- GIVEN a complaint arrives via email to klachten@gemeente.nl
-- WHEN the n8n email trigger processes the incoming email
-- THEN a complaint object MUST be auto-created with `ontvangstkanaal` set to `email`
-- AND the email body MUST be stored as `omschrijving`
-- AND the sender's email MUST be stored in `klager.email`
-- AND the complaint handler MUST receive a notification to review and complete the intake
+- **GIVEN** a complaint arrives via email to klachten@gemeente.nl
+- **WHEN** the n8n email trigger processes the incoming email
+- **THEN** a complaint object MUST be auto-created with `ontvangstkanaal` set to `email`
+- **AND** the email body MUST be stored as `omschrijving`
+- **AND** the sender's email MUST be stored in `klager.email`
+- **AND** the complaint handler MUST receive a notification to review and complete the intake
#### Scenario: Complaint data validation
-- GIVEN a case worker is creating a new complaint
-- WHEN they attempt to save without filling required fields (`onderwerp`, `omschrijving`, `ontvangstdatum`)
-- THEN the system MUST display validation errors for each missing required field
-- AND the complaint MUST NOT be saved until validation passes
+- **GIVEN** a case worker is creating a new complaint
+- **WHEN** they attempt to save without filling required fields (`onderwerp`, `omschrijving`, `ontvangstdatum`)
+- **THEN** the system MUST display validation errors for each missing required field
+- **AND** the complaint MUST NOT be saved until validation passes
### Requirement: Complaints MUST follow the Awb chapter 9 lifecycle with enforced deadlines
The Awb prescribes specific complaint handling timelines that the system MUST calculate and enforce.
#### Scenario: Awb deadline calculation on complaint creation
-- GIVEN complaint `KL-2026-0042` is received on 2026-03-01 (Monday)
-- WHEN the complaint is created
-- THEN the system MUST automatically calculate:
+- **GIVEN** complaint `KL-2026-0042` is received on 2026-03-01 (Monday)
+- **WHEN** the complaint is created
+- **THEN** the system MUST automatically calculate:
- `ontvangstbevestigingDeadline`: 5 working days = 2026-03-08 (following Monday, skipping weekend)
- `afhandelDeadline`: 6 weeks = 2026-04-12
- `verdagingMogelijk`: true (4-week extension available, extending to 2026-05-10)
-- AND these deadlines MUST be stored on the complaint object
+- **AND** these deadlines MUST be stored on the complaint object
#### Scenario: Complaint lifecycle status transitions
-- GIVEN complaint `KL-2026-0042` with status `ontvangen`
-- THEN the following status transitions MUST be enforced:
+- **GIVEN** complaint `KL-2026-0042` with status `ontvangen`
+- **THEN** the following status transitions MUST be enforced:
- `ontvangen` -> `ontvangst_bevestigd` (acknowledgment sent)
- `ontvangst_bevestigd` -> `in_behandeling` (investigation started)
- `in_behandeling` -> `hoorgesprek_gepland` (hearing scheduled)
- `hoorgesprek_gepland` -> `hoorgesprek_afgerond` (hearing completed)
- `hoorgesprek_afgerond` -> `afgehandeld` (resolution with disposition)
- Any status -> `ingetrokken` (complainant withdraws)
-- AND skipping the hearing stages MUST be allowed when the complainant waives the right to be heard
+- **AND** skipping the hearing stages MUST be allowed when the complainant waives the right to be heard
#### Scenario: Acknowledgment deadline warning at 3 days
-- GIVEN complaint `KL-2026-0042` received on 2026-03-01 with `ontvangstbevestigingDeadline` 2026-03-08
-- AND the current date is 2026-03-05 (3 working days elapsed)
-- AND status is still `ontvangen` (no acknowledgment sent)
-- THEN the system MUST send a warning notification to the complaint handler
-- AND the complaint MUST appear in the "Dreigend verlopen" section of the complaints dashboard
+- **GIVEN** complaint `KL-2026-0042` received on 2026-03-01 with `ontvangstbevestigingDeadline` 2026-03-08
+- **AND** the current date is 2026-03-05 (3 working days elapsed)
+- **AND** status is still `ontvangen` (no acknowledgment sent)
+- **THEN** the system MUST send a warning notification to the complaint handler
+- **AND** the complaint MUST appear in the "Dreigend verlopen" section of the complaints dashboard
#### Scenario: Resolution deadline warning and escalation
-- GIVEN complaint `KL-2026-0042` has `afhandelDeadline` 2026-04-12
-- AND the current date is 2026-04-05 (1 week before deadline)
-- AND status is `in_behandeling`
-- THEN the system MUST send a warning to the handler and their coordinator
-- AND if the deadline passes without resolution, the complaint MUST be flagged as "Verlopen"
-- AND the coordinator MUST receive an escalation notification
+- **GIVEN** complaint `KL-2026-0042` has `afhandelDeadline` 2026-04-12
+- **AND** the current date is 2026-04-05 (1 week before deadline)
+- **AND** status is `in_behandeling`
+- **THEN** the system MUST send a warning to the handler and their coordinator
+- **AND** if the deadline passes without resolution, the complaint MUST be flagged as "Verlopen"
+- **AND** the coordinator MUST receive an escalation notification
#### Scenario: Request deadline extension (verdaging)
-- GIVEN complaint `KL-2026-0042` has `afhandelDeadline` 2026-04-12 and `verdagingMogelijk` is true
-- WHEN the handler requests a 4-week extension with written justification
-- THEN `afhandelDeadline` MUST be updated to 2026-05-10
-- AND `verdagingMogelijk` MUST be set to false (only one extension allowed per Awb)
-- AND the complainant MUST be notified of the extension with the justification
-- AND the extension MUST be recorded in the audit trail
+- **GIVEN** complaint `KL-2026-0042` has `afhandelDeadline` 2026-04-12 and `verdagingMogelijk` is true
+- **WHEN** the handler requests a 4-week extension with written justification
+- **THEN** `afhandelDeadline` MUST be updated to 2026-05-10
+- **AND** `verdagingMogelijk` MUST be set to false (only one extension allowed per Awb)
+- **AND** the complainant MUST be notified of the extension with the justification
+- **AND** the extension MUST be recorded in the audit trail
### Requirement: Complaints MUST support a hearing (hoorgesprek)
The system SHALL support a hearing (hoorgesprek) process, as the Awb gives the complainant the right to be heard before a decision is made on the complaint.
#### Scenario: Schedule a hearing
-- GIVEN complaint `KL-2026-0042` is `in_behandeling`
-- WHEN the handler schedules a hearing
-- THEN a hearing record MUST be created as a linked object with:
+- **GIVEN** complaint `KL-2026-0042` is `in_behandeling`
+- **WHEN** the handler schedules a hearing
+- **THEN** a hearing record MUST be created as a linked object with:
- `datum`: scheduled date and time
- `locatie`: location (physical address or video conferencing link)
- `deelnemers`: list of participants (klager, behandelaar, betrokken medewerker, optional witnesses)
- `type`: hearing type (`fysiek`, `telefonisch`, `videogesprek`)
-- AND the complaint status MUST change to `hoorgesprek_gepland`
-- AND calendar invitations MUST be sent to all participants via Nextcloud Calendar (`OCP\Calendar\IManager`)
+- **AND** the complaint status MUST change to `hoorgesprek_gepland`
+- **AND** calendar invitations MUST be sent to all participants via Nextcloud Calendar (`OCP\Calendar\IManager`)
#### Scenario: Record hearing outcome
-- GIVEN the hearing for `KL-2026-0042` has taken place
-- WHEN the handler records the outcome
-- THEN the hearing record MUST be updated with:
+- **GIVEN** the hearing for `KL-2026-0042` has taken place
+- **WHEN** the handler records the outcome
+- **THEN** the hearing record MUST be updated with:
- `verslag`: summary of the hearing (mandatory)
- `conclusie`: preliminary conclusion
- `aanwezigen`: actual attendees (may differ from planned participants)
- `datumAfgerond`: actual hearing date
-- AND the complaint status MUST change to `hoorgesprek_afgerond`
+- **AND** the complaint status MUST change to `hoorgesprek_afgerond`
#### Scenario: Complainant waives right to hearing
-- GIVEN complaint `KL-2026-0042` is `in_behandeling`
-- WHEN the complainant explicitly waives their right to be heard
-- THEN the handler MUST record the waiver with: waiver date, method (email/brief/telefoon), and confirmation text
-- AND the complaint MUST skip the hearing stages and proceed directly to disposition
-- AND the waiver MUST be stored as a document attached to the complaint
+- **GIVEN** complaint `KL-2026-0042` is `in_behandeling`
+- **WHEN** the complainant explicitly waives their right to be heard
+- **THEN** the handler MUST record the waiver with: waiver date, method (email/brief/telefoon), and confirmation text
+- **AND** the complaint MUST skip the hearing stages and proceed directly to disposition
+- **AND** the waiver MUST be stored as a document attached to the complaint
#### Scenario: Hearing with video conferencing integration
-- GIVEN the hearing type is `videogesprek`
-- WHEN the hearing is scheduled
-- THEN the system MUST create a Talk conversation (via `OCP\Talk\IBroker`) and attach the link to the hearing record
-- AND the video link MUST be included in the calendar invitation
+- **GIVEN** the hearing type is `videogesprek`
+- **WHEN** the hearing is scheduled
+- **THEN** the system MUST create a Talk conversation (via `OCP\Talk\IBroker`) and attach the link to the hearing record
+- **AND** the video link MUST be included in the calendar invitation
### Requirement: Complaints MUST support escalation to formal cases
The system SHALL support escalation of a complaint to a formal case (zaak) when a complaint reveals a larger issue, while maintaining the bidirectional link.
#### Scenario: Escalate complaint to formal case
-- GIVEN complaint `KL-2026-0042` reveals a systemic service failure in the building permits department
-- WHEN the handler clicks "Escaleren naar zaak" and selects zaaktype "Intern onderzoek"
-- THEN a new zaak MUST be created in Procest with the selected zaaktype
-- AND the zaak MUST reference the originating complaint (`bronKlacht`: complaint ID)
-- AND the complaint MUST reference the created zaak (`geescaleerdeZaak`: case ID)
-- AND the complaint's documents and hearing records MUST be accessible from the zaak
-- AND the complaint status MUST remain independently trackable (not closed by escalation)
+- **GIVEN** complaint `KL-2026-0042` reveals a systemic service failure in the building permits department
+- **WHEN** the handler clicks "Escaleren naar zaak" and selects zaaktype "Intern onderzoek"
+- **THEN** a new zaak MUST be created in Procest with the selected zaaktype
+- **AND** the zaak MUST reference the originating complaint (`bronKlacht`: complaint ID)
+- **AND** the complaint MUST reference the created zaak (`geescaleerdeZaak`: case ID)
+- **AND** the complaint's documents and hearing records MUST be accessible from the zaak
+- **AND** the complaint status MUST remain independently trackable (not closed by escalation)
#### Scenario: View escalated case from complaint
-- GIVEN complaint `KL-2026-0042` has been escalated to case "ZAAK-2026-000567"
-- WHEN viewing the complaint detail
-- THEN a "Gerelateerde zaak" section MUST show the linked case with: case number, status, and a link to the case detail
-- AND updates to the case MUST be visible in the complaint's activity timeline
+- **GIVEN** complaint `KL-2026-0042` has been escalated to case "ZAAK-2026-000567"
+- **WHEN** viewing the complaint detail
+- **THEN** a "Gerelateerde zaak" section MUST show the linked case with: case number, status, and a link to the case detail
+- **AND** updates to the case MUST be visible in the complaint's activity timeline
#### Scenario: Multiple complaints escalate to same case
-- GIVEN 3 complaints about the same department issue are received
-- WHEN the handler escalates all 3 to the same case
-- THEN the case MUST reference all 3 complaints
-- AND each complaint MUST reference the case
-- AND the case detail MUST show all linked complaints
+- **GIVEN** 3 complaints about the same department issue are received
+- **WHEN** the handler escalates all 3 to the same case
+- **THEN** the case MUST reference all 3 complaints
+- **AND** each complaint MUST reference the case
+- **AND** the case detail MUST show all linked complaints
### Requirement: Disposition tracking MUST record how complaints are resolved
The system SHALL record how complaints are resolved through a formal disposition (oordeel) that classifies the outcome.
#### Scenario: Close complaint with disposition
-- GIVEN complaint `KL-2026-0042` has been investigated and the hearing is completed
-- WHEN the handler closes the complaint
-- THEN a disposition MUST be recorded with:
+- **GIVEN** complaint `KL-2026-0042` has been investigated and the hearing is completed
+- **WHEN** the handler closes the complaint
+- **THEN** a disposition MUST be recorded with:
- `oordeel`: enum (`gegrond`, `deels_gegrond`, `ongegrond`, `ingetrokken`, `niet_ontvankelijk`)
- `toelichting`: explanation of the judgment (mandatory for `gegrond` and `deels_gegrond`)
- `maatregelen`: actions taken or promised (structured list with description and responsible party)
- `afsluitdatum`: date of closure
- `afsluitbrief`: reference to the formal response letter document
-- AND the complaint status MUST change to `afgehandeld`
+- **AND** the complaint status MUST change to `afgehandeld`
#### Scenario: Disposition requires coordinator approval
-- GIVEN the tenant is configured to require approval for complaint dispositions
-- WHEN the handler submits a disposition with oordeel `gegrond`
-- THEN the disposition MUST enter `wacht_op_goedkeuring` state
-- AND the coordinator MUST receive a task to review and approve or reject the disposition
-- AND the complaint deadline timer MUST continue running during approval
+- **GIVEN** the tenant is configured to require approval for complaint dispositions
+- **WHEN** the handler submits a disposition with oordeel `gegrond`
+- **THEN** the disposition MUST enter `wacht_op_goedkeuring` state
+- **AND** the coordinator MUST receive a task to review and approve or reject the disposition
+- **AND** the complaint deadline timer MUST continue running during approval
#### Scenario: Generate formal response letter
-- GIVEN complaint `KL-2026-0042` has disposition `deels_gegrond` with maatregelen
-- WHEN the handler clicks "Afsluitbrief genereren"
-- THEN the system MUST generate a response letter using the complaint template (via Docudesk integration)
-- AND the letter MUST include: complaint number, subject, disposition, explanation, and proposed measures
-- AND the letter MUST be stored as a document linked to the complaint
+- **GIVEN** complaint `KL-2026-0042` has disposition `deels_gegrond` with maatregelen
+- **WHEN** the handler clicks "Afsluitbrief genereren"
+- **THEN** the system MUST generate a response letter using the complaint template (via Docudesk integration)
+- **AND** the letter MUST include: complaint number, subject, disposition, explanation, and proposed measures
+- **AND** the letter MUST be stored as a document linked to the complaint
#### Scenario: Disposition statistics
-- GIVEN 100 complaints were closed in Q1 2026
-- WHEN a manager views the disposition report
-- THEN the system MUST show: gegrond (15%), deels_gegrond (25%), ongegrond (45%), ingetrokken (10%), niet_ontvankelijk (5%)
-- AND the percentages MUST be broken down by category and department
+- **GIVEN** 100 complaints were closed in Q1 2026
+- **WHEN** a manager views the disposition report
+- **THEN** the system MUST show: gegrond (15%), deels_gegrond (25%), ongegrond (45%), ingetrokken (10%), niet_ontvankelijk (5%)
+- **AND** the percentages MUST be broken down by category and department
### Requirement: Frequency analysis MUST detect patterns in complaints
The system SHALL detect patterns in complaints, as recurring complaints about the same subject, department, or employee signal systemic issues that require management attention.
#### Scenario: Complaint frequency dashboard
-- GIVEN 5 complaints in the last quarter are about waiting times at the balie
-- WHEN a manager views the complaint analytics dashboard
-- THEN the system MUST show:
+- **GIVEN** 5 complaints in the last quarter are about waiting times at the balie
+- **WHEN** a manager views the complaint analytics dashboard
+- **THEN** the system MUST show:
- Complaint frequency by category (bar chart)
- Complaint frequency by department (bar chart)
- Complaint frequency by intake channel
- Trend over time (line chart, monthly granularity)
- Average resolution time by category
-- AND categories with significantly increased frequency (>50% increase vs. previous quarter) MUST be flagged
+- **AND** categories with significantly increased frequency (>50% increase vs. previous quarter) MUST be flagged
#### Scenario: Employee complaint threshold alert
-- GIVEN 3 complaints in the last 6 months reference the same `betrokkenMedewerker`
-- WHEN the threshold of 3 complaints per employee per 6 months is exceeded
-- THEN the system MUST alert the HR coordinator and the department head
-- AND the alert MUST include: employee reference (anonymized in the notification), complaint count, categories, and periods
-- AND the alert MUST NOT be visible to the regular complaint handlers (privacy protection)
+- **GIVEN** 3 complaints in the last 6 months reference the same `betrokkenMedewerker`
+- **WHEN** the threshold of 3 complaints per employee per 6 months is exceeded
+- **THEN** the system MUST alert the HR coordinator and the department head
+- **AND** the alert MUST include: employee reference (anonymized in the notification), complaint count, categories, and periods
+- **AND** the alert MUST NOT be visible to the regular complaint handlers (privacy protection)
#### Scenario: Systemic issue detection
-- GIVEN complaint categories `wachttijd_balie` and `telefonische_bereikbaarheid` both show >100% increase in Q1 2026
-- WHEN the quarterly analysis runs
-- THEN the system MUST generate a "Systeemmelding" with: affected categories, complaint counts, trend direction, and suggested action
-- AND the systemic issue report MUST be exportable as PDF for management reporting
+- **GIVEN** complaint categories `wachttijd_balie` and `telefonische_bereikbaarheid` both show >100% increase in Q1 2026
+- **WHEN** the quarterly analysis runs
+- **THEN** the system MUST generate a "Systeemmelding" with: affected categories, complaint counts, trend direction, and suggested action
+- **AND** the systemic issue report MUST be exportable as PDF for management reporting
#### Scenario: Benchmarking against targets
-- GIVEN the municipality has set targets: max 10 complaints/month, >90% resolved within Awb deadline, <15% gegrond rate
-- WHEN the dashboard loads
-- THEN KPI cards MUST show actual vs. target for each metric
-- AND metrics exceeding targets MUST be highlighted in red
+- **GIVEN** the municipality has set targets: max 10 complaints/month, >90% resolved within Awb deadline, <15% gegrond rate
+- **WHEN** the dashboard loads
+- **THEN** KPI cards MUST show actual vs. target for each metric
+- **AND** metrics exceeding targets MUST be highlighted in red
### Requirement: Complaint categories MUST be configurable per tenant
The system SHALL support configurable complaint categories per tenant, allowing each municipality to define its own categories to match their organizational structure.
#### Scenario: Configure complaint categories
-- GIVEN a tenant admin accesses Settings > Klachtcategorieen
-- WHEN they define categories
-- THEN they MUST be able to create, edit, and deactivate categories with: name, description, default handler (user or group), and SLA override (custom deadline)
-- AND default categories MUST be pre-configured: "Dienstverlening", "Bejegening", "Wachttijd", "Informatievoorziening", "Procedures"
+- **GIVEN** a tenant admin accesses Settings > Klachtcategorieen
+- **WHEN** they define categories
+- **THEN** they MUST be able to create, edit, and deactivate categories with: name, description, default handler (user or group), and SLA override (custom deadline)
+- **AND** default categories MUST be pre-configured: "Dienstverlening", "Bejegening", "Wachttijd", "Informatievoorziening", "Procedures"
#### Scenario: Category-specific routing
-- GIVEN category "Bejegening" has default handler set to group "HR-Klachten"
-- WHEN a complaint is created with category "Bejegening"
-- THEN the complaint MUST be automatically assigned to the "HR-Klachten" group
-- AND a member of the group MUST be able to claim the complaint
+- **GIVEN** category "Bejegening" has default handler set to group "HR-Klachten"
+- **WHEN** a complaint is created with category "Bejegening"
+- **THEN** the complaint MUST be automatically assigned to the "HR-Klachten" group
+- **AND** a member of the group MUST be able to claim the complaint
#### Scenario: Deactivate category without data loss
-- GIVEN category "Legacy categorie" has 15 historical complaints
-- WHEN the admin deactivates the category
-- THEN new complaints MUST NOT be assignable to this category
-- AND existing complaints with this category MUST retain their category value
-- AND the category MUST still appear in historical reports
+- **GIVEN** category "Legacy categorie" has 15 historical complaints
+- **WHEN** the admin deactivates the category
+- **THEN** new complaints MUST NOT be assignable to this category
+- **AND** existing complaints with this category MUST retain their category value
+- **AND** the category MUST still appear in historical reports
### Requirement: Complaint views MUST integrate with the Procest dashboard
Complaints MUST be accessible through dedicated views and dashboard widgets.
#### Scenario: Complaint list view
-- GIVEN the complaints module is enabled
-- WHEN a complaint handler navigates to "Klachten" in the sidebar
-- THEN a list view MUST show all complaints assigned to them with: complaint number, subject, category, status, received date, deadline, and days remaining
-- AND overdue complaints MUST be sorted to the top and highlighted in red
-- AND the list MUST support filtering by: status, category, handler, date range, and priority
+- **GIVEN** the complaints module is enabled
+- **WHEN** a complaint handler navigates to "Klachten" in the sidebar
+- **THEN** a list view MUST show all complaints assigned to them with: complaint number, subject, category, status, received date, deadline, and days remaining
+- **AND** overdue complaints MUST be sorted to the top and highlighted in red
+- **AND** the list MUST support filtering by: status, category, handler, date range, and priority
#### Scenario: Complaint detail view
-- GIVEN complaint `KL-2026-0042` exists
-- WHEN the handler clicks on it in the complaint list
-- THEN a detail view MUST show: all complaint fields, status timeline, deadline panel (reusing DeadlinePanel.vue), hearing records, linked documents, activity timeline, and linked case (if escalated)
-- AND the handler MUST be able to change status, schedule hearing, record disposition, and escalate to case from this view
+- **GIVEN** complaint `KL-2026-0042` exists
+- **WHEN** the handler clicks on it in the complaint list
+- **THEN** a detail view MUST show: all complaint fields, status timeline, deadline panel (reusing DeadlinePanel.vue), hearing records, linked documents, activity timeline, and linked case (if escalated)
+- **AND** the handler MUST be able to change status, schedule hearing, record disposition, and escalate to case from this view
#### Scenario: Dashboard complaint widget
-- GIVEN the Procest dashboard (Dashboard.vue)
-- WHEN a complaint handler views their dashboard
-- THEN a "Mijn klachten" widget MUST show: open complaints count, overdue count, and upcoming deadlines (next 5 working days)
-- AND clicking the widget MUST navigate to the filtered complaint list
+- **GIVEN** the Procest dashboard (Dashboard.vue)
+- **WHEN** a complaint handler views their dashboard
+- **THEN** a "Mijn klachten" widget MUST show: open complaints count, overdue count, and upcoming deadlines (next 5 working days)
+- **AND** clicking the widget MUST navigate to the filtered complaint list
#### Scenario: Complaint KPI cards on management dashboard
-- GIVEN a coordinator or manager views the dashboard
-- THEN complaint KPI cards MUST show: total complaints this month, average resolution time, Awb compliance rate (% resolved within deadline), and disposition breakdown (gegrond/ongegrond pie chart)
+- **GIVEN** a coordinator or manager views the dashboard
+- **THEN** complaint KPI cards MUST show: total complaints this month, average resolution time, Awb compliance rate (% resolved within deadline), and disposition breakdown (gegrond/ongegrond pie chart)
### Requirement: Complainant communication MUST be tracked
All communication with the complainant MUST be recorded as part of the complaint record.
#### Scenario: Send acknowledgment letter
-- GIVEN complaint `KL-2026-0042` is in status `ontvangen`
-- WHEN the handler clicks "Ontvangstbevestiging verzenden"
-- THEN a template letter MUST be generated (via Docudesk) with: complaint number, received date, handler name, and expected resolution date
-- AND the letter MUST be sent via the configured channel (email or print queue)
-- AND the complaint status MUST change to `ontvangst_bevestigd`
-- AND the sent letter MUST be stored as a document linked to the complaint
+- **GIVEN** complaint `KL-2026-0042` is in status `ontvangen`
+- **WHEN** the handler clicks "Ontvangstbevestiging verzenden"
+- **THEN** a template letter MUST be generated (via Docudesk) with: complaint number, received date, handler name, and expected resolution date
+- **AND** the letter MUST be sent via the configured channel (email or print queue)
+- **AND** the complaint status MUST change to `ontvangst_bevestigd`
+- **AND** the sent letter MUST be stored as a document linked to the complaint
#### Scenario: Track phone call with complainant
-- GIVEN the handler makes a phone call to the complainant
-- WHEN they record the call in the complaint
-- THEN a communication record MUST be created with: date, duration, summary, and follow-up actions
-- AND the communication MUST appear in the complaint's activity timeline
+- **GIVEN** the handler makes a phone call to the complainant
+- **WHEN** they record the call in the complaint
+- **THEN** a communication record MUST be created with: date, duration, summary, and follow-up actions
+- **AND** the communication MUST appear in the complaint's activity timeline
#### Scenario: Complainant submits additional information
-- GIVEN complaint `KL-2026-0042` is `in_behandeling`
-- WHEN the complainant sends additional documents via email
-- THEN the n8n email handler MUST link the attachments to the existing complaint (matching on complaint number in subject line)
-- AND the handler MUST receive a notification about the new attachments
+- **GIVEN** complaint `KL-2026-0042` is `in_behandeling`
+- **WHEN** the complainant sends additional documents via email
+- **THEN** the n8n email handler MUST link the attachments to the existing complaint (matching on complaint number in subject line)
+- **AND** the handler MUST receive a notification about the new attachments
## Non-Requirements
- This spec does NOT cover bezwaarschriften (formal objections to decisions) -- these have a different legal process
diff --git a/openspec/changes/consultation-management/specs/consultation-management/spec.md b/openspec/changes/consultation-management/specs/consultation-management/spec.md
index fbc840eb..4856744b 100644
--- a/openspec/changes/consultation-management/specs/consultation-management/spec.md
+++ b/openspec/changes/consultation-management/specs/consultation-management/spec.md
@@ -11,15 +11,14 @@ Dutch government case processing frequently requires consultation between depart
Procest's case infrastructure (cases, tasks, roles, statuses, documents) provides the foundation. ArkCase implements consultations as a full entity with its own pipeline, status lifecycle, document management, and department assignment -- essentially a "mini-case" linked to a parent case. Procest can achieve similar functionality using OpenRegister linked objects with a dedicated consultation schema and n8n workflows for lifecycle management.
-## Requirements
-
+## ADDED Requirements
### Requirement: Consultations MUST be first-class entities linked to parent cases
The system SHALL store consultations as first-class entities in OpenRegister with a dedicated schema, linked to a parent case via object relations.
#### Scenario: Create a consultation for a case
-- GIVEN case `ZAAK-2026-000123` (type: `omgevingsvergunning`) requires fire safety advice
-- WHEN a case worker clicks "Advies aanvragen" on the case detail view
-- THEN a consultation creation dialog MUST appear with fields:
+- **GIVEN** case `ZAAK-2026-000123` (type: `omgevingsvergunning`) requires fire safety advice
+- **WHEN** a case worker clicks "Advies aanvragen" on the case detail view
+- **THEN** a consultation creation dialog MUST appear with fields:
- `parentZaak`: pre-filled with `ZAAK-2026-000123` (read-only)
- `adviesInstantie`: the department or organization being consulted (searchable dropdown)
- `onderwerp`: subject of the consultation (pre-filled with case title, editable)
@@ -27,150 +26,150 @@ The system SHALL store consultations as first-class entities in OpenRegister wit
- `uiterlijkeReactiedatum`: the deadline for response (date picker, default: 4 weeks from now)
- `prioriteit`: priority level (`normaal`, `spoed`)
- `bijlagen`: documents to include from the parent case (multi-select from case documents)
-- AND upon save, a consultation object MUST be created in OpenRegister with status `open`
-- AND the consultation number MUST be auto-generated (format: `ADV-{year}-{sequence}`)
+- **AND** upon save, a consultation object MUST be created in OpenRegister with status `open`
+- **AND** the consultation number MUST be auto-generated (format: `ADV-{year}-{sequence}`)
#### Scenario: Multiple consultations per case with independent lifecycles
-- GIVEN case `ZAAK-2026-000123` needs advice from both Brandweer and Welstandscommissie
-- WHEN the case worker creates two consultations
-- THEN both MUST be visible in the case's "Adviezen" tab
-- AND each MUST have independent status, deadline, and document exchange
-- AND the case detail MUST show a consultation count badge on the "Adviezen" tab
+- **GIVEN** case `ZAAK-2026-000123` needs advice from both Brandweer and Welstandscommissie
+- **WHEN** the case worker creates two consultations
+- **THEN** both MUST be visible in the case's "Adviezen" tab
+- **AND** each MUST have independent status, deadline, and document exchange
+- **AND** the case detail MUST show a consultation count badge on the "Adviezen" tab
#### Scenario: Consultation references parent case bidirectionally
-- GIVEN consultation `ADV-2026-0015` is created for case `ZAAK-2026-000123`
-- THEN the consultation MUST have a `parentZaak` field referencing the case
-- AND the case MUST have a `consultations` relation listing all linked consultations
-- AND navigating from consultation to case and vice versa MUST be possible via clickable links
+- **GIVEN** consultation `ADV-2026-0015` is created for case `ZAAK-2026-000123`
+- **THEN** the consultation MUST have a `parentZaak` field referencing the case
+- **AND** the case MUST have a `consultations` relation listing all linked consultations
+- **AND** navigating from consultation to case and vice versa MUST be possible via clickable links
#### Scenario: Consultation data validation
-- GIVEN a case worker is creating a consultation
-- WHEN they attempt to save without filling `adviesInstantie`, `vraagstelling`, or `uiterlijkeReactiedatum`
-- THEN the system MUST display validation errors for each missing required field
-- AND the consultation MUST NOT be saved until validation passes
+- **GIVEN** a case worker is creating a consultation
+- **WHEN** they attempt to save without filling `adviesInstantie`, `vraagstelling`, or `uiterlijkeReactiedatum`
+- **THEN** the system MUST display validation errors for each missing required field
+- **AND** the consultation MUST NOT be saved until validation passes
### Requirement: Consultations MUST have their own lifecycle with deadline enforcement
The system SHALL support an independent consultation lifecycle with status progression and configurable deadline warnings, independent of the parent case.
#### Scenario: Consultation lifecycle status transitions
-- GIVEN consultation `ADV-2026-0015` is created with status `open`
-- THEN the following status transitions MUST be enforced:
+- **GIVEN** consultation `ADV-2026-0015` is created with status `open`
+- **THEN** the following status transitions MUST be enforced:
- `open` -> `ontvangen` (consulted department acknowledges receipt)
- `ontvangen` -> `in_behandeling` (department starts working on the advice)
- `in_behandeling` -> `advies_uitgebracht` (department submits their advice)
- `advies_uitgebracht` -> `afgesloten` (case worker reviews and closes the consultation)
- Any open status -> `ingetrokken` (case worker withdraws the consultation request)
-- AND backward transitions MUST NOT be allowed except by coordinator role
+- **AND** backward transitions MUST NOT be allowed except by coordinator role
#### Scenario: Consulted department acknowledges receipt
-- GIVEN consultation `ADV-2026-0015` has status `open`
-- WHEN the Brandweer department user views their consultation inbox
-- AND clicks "Ontvangen" on the consultation
-- THEN the status MUST change to `ontvangen`
-- AND the acknowledgment timestamp and user MUST be recorded
-- AND the requesting case worker MUST receive a notification: "Adviesaanvraag ADV-2026-0015 ontvangen door Brandweer"
+- **GIVEN** consultation `ADV-2026-0015` has status `open`
+- **WHEN** the Brandweer department user views their consultation inbox
+- **AND** clicks "Ontvangen" on the consultation
+- **THEN** the status MUST change to `ontvangen`
+- **AND** the acknowledgment timestamp and user MUST be recorded
+- **AND** the requesting case worker MUST receive a notification: "Adviesaanvraag ADV-2026-0015 ontvangen door Brandweer"
#### Scenario: Deadline warning at 5 days before due
-- GIVEN consultation `ADV-2026-0015` has `uiterlijkeReactiedatum` of 2026-04-15
-- AND the current date is 2026-04-10
-- AND the status is `in_behandeling`
-- THEN the system MUST send a warning notification to both the consulted department and the requesting case worker
-- AND the consultation MUST appear highlighted in amber in all views
+- **GIVEN** consultation `ADV-2026-0015` has `uiterlijkeReactiedatum` of 2026-04-15
+- **AND** the current date is 2026-04-10
+- **AND** the status is `in_behandeling`
+- **THEN** the system MUST send a warning notification to both the consulted department and the requesting case worker
+- **AND** the consultation MUST appear highlighted in amber in all views
#### Scenario: Overdue consultation escalation
-- GIVEN consultation `ADV-2026-0015` has `uiterlijkeReactiedatum` of 2026-04-15
-- AND the current date is 2026-04-16
-- AND the status is still `in_behandeling`
-- THEN the consultation MUST be flagged as overdue (red highlight)
-- AND a notification MUST be sent to the requesting case worker, the consulted department head, and the parent case's coordinator
-- AND the overdue consultation MUST appear in the "Verlopen adviezen" section of the dashboard
+- **GIVEN** consultation `ADV-2026-0015` has `uiterlijkeReactiedatum` of 2026-04-15
+- **AND** the current date is 2026-04-16
+- **AND** the status is still `in_behandeling`
+- **THEN** the consultation MUST be flagged as overdue (red highlight)
+- **AND** a notification MUST be sent to the requesting case worker, the consulted department head, and the parent case's coordinator
+- **AND** the overdue consultation MUST appear in the "Verlopen adviezen" section of the dashboard
#### Scenario: Request deadline extension
-- GIVEN consultation `ADV-2026-0015` is `in_behandeling` with deadline 2026-04-15
-- WHEN the consulted department requests a 2-week extension with justification "Externe expertise nodig"
-- THEN the requesting case worker MUST receive an extension request notification
-- AND the case worker MUST approve or reject the extension
-- AND upon approval, the deadline MUST be updated to 2026-04-29
-- AND the extension MUST be recorded in the consultation's audit trail
+- **GIVEN** consultation `ADV-2026-0015` is `in_behandeling` with deadline 2026-04-15
+- **WHEN** the consulted department requests a 2-week extension with justification "Externe expertise nodig"
+- **THEN** the requesting case worker MUST receive an extension request notification
+- **AND** the case worker MUST approve or reject the extension
+- **AND** upon approval, the deadline MUST be updated to 2026-04-29
+- **AND** the extension MUST be recorded in the consultation's audit trail
### Requirement: Consultations MUST support structured document exchange
The system SHALL support structured document exchange, allowing both the requester and the consulted party to attach and exchange documents within the consultation context.
#### Scenario: Attach context documents from parent case
-- GIVEN case `ZAAK-2026-000123` has 5 documents including building plans and site photos
-- WHEN creating consultation `ADV-2026-0015` for fire safety advice
-- THEN the case worker MUST be able to select relevant documents from the case's document list
-- AND selected documents MUST be linked to the consultation (not copied) via OpenRegister relations
-- AND the consulted department MUST be able to view those documents from the consultation detail
+- **GIVEN** case `ZAAK-2026-000123` has 5 documents including building plans and site photos
+- **WHEN** creating consultation `ADV-2026-0015` for fire safety advice
+- **THEN** the case worker MUST be able to select relevant documents from the case's document list
+- **AND** selected documents MUST be linked to the consultation (not copied) via OpenRegister relations
+- **AND** the consulted department MUST be able to view those documents from the consultation detail
#### Scenario: Consulted party uploads advice document
-- GIVEN consultation `ADV-2026-0015` is `in_behandeling`
-- WHEN the Brandweer user uploads their formal advice as "brandveiligheidsadvies-2026.pdf"
-- THEN the document MUST be stored in the case's Nextcloud folder under subfolder "Adviezen/ADV-2026-0015/"
-- AND the document MUST be linked to both the consultation and the parent case
-- AND the requesting case worker MUST receive a notification: "Document ontvangen: brandveiligheidsadvies-2026.pdf van Brandweer"
+- **GIVEN** consultation `ADV-2026-0015` is `in_behandeling`
+- **WHEN** the Brandweer user uploads their formal advice as "brandveiligheidsadvies-2026.pdf"
+- **THEN** the document MUST be stored in the case's Nextcloud folder under subfolder "Adviezen/ADV-2026-0015/"
+- **AND** the document MUST be linked to both the consultation and the parent case
+- **AND** the requesting case worker MUST receive a notification: "Document ontvangen: brandveiligheidsadvies-2026.pdf van Brandweer"
#### Scenario: Document version management
-- GIVEN the Brandweer uploads an initial advice document
-- AND later uploads a revised version with corrections
-- THEN both versions MUST be preserved (Nextcloud file versioning)
-- AND the consultation's document list MUST show the latest version with a "Versiegeschiedenis" link
-- AND the case worker MUST be notified of the revision
+- **GIVEN** the Brandweer uploads an initial advice document
+- **AND** later uploads a revised version with corrections
+- **THEN** both versions MUST be preserved (Nextcloud file versioning)
+- **AND** the consultation's document list MUST show the latest version with a "Versiegeschiedenis" link
+- **AND** the case worker MUST be notified of the revision
#### Scenario: Document access scoping
-- GIVEN consultation `ADV-2026-0015` links 3 documents from the parent case
-- WHEN the consulted department user views the consultation
-- THEN they MUST see only the 3 linked documents, NOT all parent case documents
-- AND they MUST NOT be able to access other case documents or other cases
+- **GIVEN** consultation `ADV-2026-0015` links 3 documents from the parent case
+- **WHEN** the consulted department user views the consultation
+- **THEN** they MUST see only the 3 linked documents, NOT all parent case documents
+- **AND** they MUST NOT be able to access other case documents or other cases
### Requirement: Consultation responses MUST be structured with formal conclusions
The system SHALL support structured consultation responses with a formal conclusion enum and optional conditions that flow back to the parent case.
#### Scenario: Submit positive advice with conditions
-- GIVEN consultation `ADV-2026-0015` asks "Is the building fire-safe?"
-- WHEN the Brandweer user submits their response
-- THEN the response form MUST include:
+- **GIVEN** consultation `ADV-2026-0015` asks "Is the building fire-safe?"
+- **WHEN** the Brandweer user submits their response
+- **THEN** the response form MUST include:
- `advies`: enum value (`positief`, `positief_met_voorwaarden`, `negatief`, `niet_van_toepassing`)
- `toelichting`: explanation text (mandatory for all values except `niet_van_toepassing`)
- `voorwaarden`: list of conditions (enabled when `positief_met_voorwaarden` is selected), each with description and priority
- `datum`: date the advice was given
- `bijlagen`: uploaded advice documents
-- AND the consultation status MUST change to `advies_uitgebracht`
-- AND the requesting case worker MUST receive a notification with the advice summary
+- **AND** the consultation status MUST change to `advies_uitgebracht`
+- **AND** the requesting case worker MUST receive a notification with the advice summary
#### Scenario: Negative advice blocks case progression
-- GIVEN consultation `ADV-2026-0015` receives advice `negatief` with toelichting "Brandtrap ontbreekt"
-- WHEN the case worker views the parent case
-- THEN the case MUST display a warning: "Negatief advies ontvangen van Brandweer"
-- AND if the case type is configured to require positive advice for this consultation type, the case MUST NOT be progressable to the decision milestone until the negative advice is addressed
+- **GIVEN** consultation `ADV-2026-0015` receives advice `negatief` with toelichting "Brandtrap ontbreekt"
+- **WHEN** the case worker views the parent case
+- **THEN** the case MUST display a warning: "Negatief advies ontvangen van Brandweer"
+- **AND** if the case type is configured to require positive advice for this consultation type, the case MUST NOT be progressable to the decision milestone until the negative advice is addressed
#### Scenario: Conditions from advice flow back as tasks on parent case
-- GIVEN consultation `ADV-2026-0015` has advice `positief_met_voorwaarden` with 3 conditions
-- WHEN the case worker views the parent case
-- THEN the conditions MUST appear as a "Voorwaarden" checklist in the case detail
-- AND each condition MUST be individually markable as addressed or not addressed
-- AND the case worker MUST be able to link evidence documents to each condition
-- AND the consultation MUST show the condition compliance status
+- **GIVEN** consultation `ADV-2026-0015` has advice `positief_met_voorwaarden` with 3 conditions
+- **WHEN** the case worker views the parent case
+- **THEN** the conditions MUST appear as a "Voorwaarden" checklist in the case detail
+- **AND** each condition MUST be individually markable as addressed or not addressed
+- **AND** the case worker MUST be able to link evidence documents to each condition
+- **AND** the consultation MUST show the condition compliance status
#### Scenario: Request clarification on advice
-- GIVEN consultation `ADV-2026-0015` has received advice that the case worker finds unclear
-- WHEN the case worker clicks "Verduidelijking vragen" on the consultation
-- THEN the consultation status MUST remain `advies_uitgebracht` (not revert to `in_behandeling`)
-- AND a clarification request MUST be sent as a comment on the consultation
-- AND the consulted department MUST receive a notification with the clarification question
+- **GIVEN** consultation `ADV-2026-0015` has received advice that the case worker finds unclear
+- **WHEN** the case worker clicks "Verduidelijking vragen" on the consultation
+- **THEN** the consultation status MUST remain `advies_uitgebracht` (not revert to `in_behandeling`)
+- **AND** a clarification request MUST be sent as a comment on the consultation
+- **AND** the consulted department MUST receive a notification with the clarification question
### Requirement: Consultation events MUST appear in the parent case timeline
The system SHALL ensure all consultation lifecycle events are visible in the parent case's activity feed for full traceability.
#### Scenario: Consultation creation event in case timeline
-- GIVEN case `ZAAK-2026-000123` has consultation `ADV-2026-0015` created
-- WHEN viewing the case's ActivityTimeline component
-- THEN the following event MUST appear: "Adviesaanvraag aangemaakt voor Brandweer (ADV-2026-0015)" with date and requester name
+- **GIVEN** case `ZAAK-2026-000123` has consultation `ADV-2026-0015` created
+- **WHEN** viewing the case's ActivityTimeline component
+- **THEN** the following event MUST appear: "Adviesaanvraag aangemaakt voor Brandweer (ADV-2026-0015)" with date and requester name
#### Scenario: Full consultation lifecycle in case timeline
-- GIVEN consultation `ADV-2026-0015` progresses through its full lifecycle
-- WHEN viewing the case timeline
-- THEN the following events MUST appear chronologically:
+- **GIVEN** consultation `ADV-2026-0015` progresses through its full lifecycle
+- **WHEN** viewing the case timeline
+- **THEN** the following events MUST appear chronologically:
- "Adviesaanvraag aangemaakt voor Brandweer" (with date and requester)
- "Brandweer heeft adviesaanvraag ontvangen" (with date)
- "Brandweer is gestart met advies" (with date)
@@ -179,125 +178,125 @@ The system SHALL ensure all consultation lifecycle events are visible in the par
- "Adviesaanvraag afgesloten" (with date and closer)
#### Scenario: Overdue consultation warning in case timeline
-- GIVEN consultation `ADV-2026-0015` is 3 days overdue
-- WHEN viewing the case timeline
-- THEN a warning event MUST appear: "Adviesaanvraag ADV-2026-0015 is verlopen (3 dagen over deadline)"
-- AND the event MUST be visually distinct (red/amber indicator)
+- **GIVEN** consultation `ADV-2026-0015` is 3 days overdue
+- **WHEN** viewing the case timeline
+- **THEN** a warning event MUST appear: "Adviesaanvraag ADV-2026-0015 is verlopen (3 dagen over deadline)"
+- **AND** the event MUST be visually distinct (red/amber indicator)
### Requirement: Consulted departments MUST have a dedicated inbox view
The system SHALL provide a dedicated inbox view for consulted departments, giving department users a centralized view of all consultations assigned to their department.
#### Scenario: Department consultation inbox
-- GIVEN the Brandweer department has 5 open consultations across different cases
-- WHEN a Brandweer user navigates to "Adviesaanvragen" in the sidebar
-- THEN all 5 consultations MUST be listed with: consultation number, parent case number, subject, requesting department, deadline, and status
-- AND overdue items MUST be sorted to the top and highlighted in red
-- AND the list MUST support filtering by: status, requesting department, date range, and priority
+- **GIVEN** the Brandweer department has 5 open consultations across different cases
+- **WHEN** a Brandweer user navigates to "Adviesaanvragen" in the sidebar
+- **THEN** all 5 consultations MUST be listed with: consultation number, parent case number, subject, requesting department, deadline, and status
+- **AND** overdue items MUST be sorted to the top and highlighted in red
+- **AND** the list MUST support filtering by: status, requesting department, date range, and priority
#### Scenario: Claim consultation for handling
-- GIVEN consultation `ADV-2026-0015` is assigned to the Brandweer department (group)
-- WHEN Brandweer user "P. Jansen" clicks "Oppakken" on the consultation
-- THEN the consultation MUST be assigned to "P. Jansen" as the individual handler
-- AND the requesting case worker MUST receive a notification: "P. Jansen (Brandweer) behandelt adviesaanvraag ADV-2026-0015"
+- **GIVEN** consultation `ADV-2026-0015` is assigned to the Brandweer department (group)
+- **WHEN** Brandweer user "P. Jansen" clicks "Oppakken" on the consultation
+- **THEN** the consultation MUST be assigned to "P. Jansen" as the individual handler
+- **AND** the requesting case worker MUST receive a notification: "P. Jansen (Brandweer) behandelt adviesaanvraag ADV-2026-0015"
#### Scenario: Reassign consultation within department
-- GIVEN consultation `ADV-2026-0015` is assigned to "P. Jansen"
-- WHEN the department coordinator reassigns it to "M. de Vries"
-- THEN the assignment MUST be updated
-- AND both "P. Jansen" and "M. de Vries" MUST receive notifications
-- AND the reassignment MUST be recorded in the consultation's audit trail
+- **GIVEN** consultation `ADV-2026-0015` is assigned to "P. Jansen"
+- **WHEN** the department coordinator reassigns it to "M. de Vries"
+- **THEN** the assignment MUST be updated
+- **AND** both "P. Jansen" and "M. de Vries" MUST receive notifications
+- **AND** the reassignment MUST be recorded in the consultation's audit trail
### Requirement: Dashboard MUST show consultation KPIs
The system SHALL provide dashboard KPIs for consultations, giving coordinators and department heads oversight of open consultations with performance metrics.
#### Scenario: My pending consultations widget
-- GIVEN a Brandweer user has 3 open consultations assigned to their department
-- WHEN they view the Procest dashboard
-- THEN a "Openstaande adviesaanvragen" widget MUST show: count of open consultations, count of overdue consultations, and the 3 nearest deadlines
-- AND clicking the widget MUST navigate to the filtered consultation inbox
+- **GIVEN** a Brandweer user has 3 open consultations assigned to their department
+- **WHEN** they view the Procest dashboard
+- **THEN** a "Openstaande adviesaanvragen" widget MUST show: count of open consultations, count of overdue consultations, and the 3 nearest deadlines
+- **AND** clicking the widget MUST navigate to the filtered consultation inbox
#### Scenario: Consultation performance metrics for coordinators
-- GIVEN 50 consultations were completed in Q1 2026
-- WHEN a coordinator views the consultation analytics
-- THEN the dashboard MUST show:
+- **GIVEN** 50 consultations were completed in Q1 2026
+- **WHEN** a coordinator views the consultation analytics
+- **THEN** the dashboard MUST show:
- Average response time by department
- On-time completion rate by department
- Advice outcome distribution (positief/negatief/voorwaarden) by consultation type
- Total consultations per case type
-- AND departments with >20% overdue rate MUST be highlighted
+- **AND** departments with >20% overdue rate MUST be highlighted
#### Scenario: Consultation bottleneck detection
-- GIVEN 8 consultations assigned to the Welstandscommissie are overdue
-- AND the average response time has increased from 10 days to 25 days in the last month
-- WHEN the daily analytics job runs
-- THEN the coordinator MUST receive an alert: "Welstandscommissie: 8 verlopen adviezen, gemiddelde doorlooptijd gestegen naar 25 dagen"
+- **GIVEN** 8 consultations assigned to the Welstandscommissie are overdue
+- **AND** the average response time has increased from 10 days to 25 days in the last month
+- **WHEN** the daily analytics job runs
+- **THEN** the coordinator MUST receive an alert: "Welstandscommissie: 8 verlopen adviezen, gemiddelde doorlooptijd gestegen naar 25 dagen"
### Requirement: Consultation types MUST be configurable per case type
The system SHALL support configurable consultation types per case type, defining which consultation types are available and whether they are mandatory or optional.
#### Scenario: Configure mandatory consultation for zaaktype
-- GIVEN zaaktype `omgevingsvergunning` is being configured
-- WHEN an admin defines consultation types
-- THEN they MUST be able to add: "Brandveiligheid" (mandatory, default department: Brandweer, default deadline: 4 weeks)
-- AND "Welstandstoets" (optional, default department: Welstandscommissie, default deadline: 3 weeks)
-- AND mandatory consultations MUST be auto-created when a case of this type is created
+- **GIVEN** zaaktype `omgevingsvergunning` is being configured
+- **WHEN** an admin defines consultation types
+- **THEN** they MUST be able to add: "Brandveiligheid" (mandatory, default department: Brandweer, default deadline: 4 weeks)
+- **AND** "Welstandstoets" (optional, default department: Welstandscommissie, default deadline: 3 weeks)
+- **AND** mandatory consultations MUST be auto-created when a case of this type is created
#### Scenario: Mandatory consultation blocks case completion
-- GIVEN case `ZAAK-2026-000123` has a mandatory consultation "Brandveiligheid" that is still `open`
-- WHEN the case worker attempts to progress the case to the "Besluit" milestone
-- THEN the system MUST block progression with message: "Verplicht advies 'Brandveiligheid' is nog niet ontvangen"
-- AND the blocking consultations MUST be listed with links
+- **GIVEN** case `ZAAK-2026-000123` has a mandatory consultation "Brandveiligheid" that is still `open`
+- **WHEN** the case worker attempts to progress the case to the "Besluit" milestone
+- **THEN** the system MUST block progression with message: "Verplicht advies 'Brandveiligheid' is nog niet ontvangen"
+- **AND** the blocking consultations MUST be listed with links
#### Scenario: Optional consultation can be skipped
-- GIVEN case `ZAAK-2026-000123` has an optional consultation "Welstandstoets" that was not created
-- WHEN the case worker progresses the case to the decision milestone
-- THEN the system MUST allow progression without the optional consultation
-- AND no warning MUST be shown for optional consultations that were never created
+- **GIVEN** case `ZAAK-2026-000123` has an optional consultation "Welstandstoets" that was not created
+- **WHEN** the case worker progresses the case to the decision milestone
+- **THEN** the system MUST allow progression without the optional consultation
+- **AND** no warning MUST be shown for optional consultations that were never created
### Requirement: Advisory bodies MUST be manageable as a registry
Departments and external advisory bodies that can receive consultations MUST be stored in a searchable registry.
#### Scenario: Configure advisory body
-- GIVEN an admin accesses Settings > Adviesinstanties
-- WHEN they add a new advisory body
-- THEN they MUST provide: name, type (internal department / external organization), default contact group (Nextcloud group), email address, and specializations (tags)
-- AND the advisory body MUST be stored as an OpenRegister object
+- **GIVEN** an admin accesses Settings > Adviesinstanties
+- **WHEN** they add a new advisory body
+- **THEN** they MUST provide: name, type (internal department / external organization), default contact group (Nextcloud group), email address, and specializations (tags)
+- **AND** the advisory body MUST be stored as an OpenRegister object
#### Scenario: Search advisory bodies by specialization
-- GIVEN 15 advisory bodies are configured, 3 of which have specialization "brandveiligheid"
-- WHEN a case worker creates a consultation and searches for "brand"
-- THEN the search results MUST show the 3 brandveiligheid-specialized bodies first
-- AND all 15 bodies MUST still be selectable
+- **GIVEN** 15 advisory bodies are configured, 3 of which have specialization "brandveiligheid"
+- **WHEN** a case worker creates a consultation and searches for "brand"
+- **THEN** the search results MUST show the 3 brandveiligheid-specialized bodies first
+- **AND** all 15 bodies MUST still be selectable
#### Scenario: External advisory body receives consultation via email
-- GIVEN advisory body "GGD Regio Utrecht" is an external organization with no Nextcloud account
-- WHEN a consultation is created for this body
-- THEN the system MUST send the consultation request via email to the configured email address
-- AND the email MUST include: consultation number, subject, question, deadline, and a secure response link
-- AND the external body MUST be able to respond via the secure link (uploading advice document and selecting advice outcome)
+- **GIVEN** advisory body "GGD Regio Utrecht" is an external organization with no Nextcloud account
+- **WHEN** a consultation is created for this body
+- **THEN** the system MUST send the consultation request via email to the configured email address
+- **AND** the email MUST include: consultation number, subject, question, deadline, and a secure response link
+- **AND** the external body MUST be able to respond via the secure link (uploading advice document and selecting advice outcome)
### Requirement: Parallel and sequential consultation patterns MUST be supported
The system SHALL support both parallel and sequential consultation patterns, as cases may require multiple consultations that can run in parallel or must complete sequentially.
#### Scenario: Parallel consultations with "wait for all" completion
-- GIVEN case `ZAAK-2026-000123` has 3 mandatory consultations (Brandweer, Welstand, Milieu)
-- WHEN all 3 are created simultaneously
-- THEN all 3 MUST run independently with their own deadlines
-- AND the case MUST NOT progress to the decision milestone until ALL 3 have status `advies_uitgebracht`
-- AND the case detail MUST show a summary: "Adviezen: 2/3 ontvangen"
+- **GIVEN** case `ZAAK-2026-000123` has 3 mandatory consultations (Brandweer, Welstand, Milieu)
+- **WHEN** all 3 are created simultaneously
+- **THEN** all 3 MUST run independently with their own deadlines
+- **AND** the case MUST NOT progress to the decision milestone until ALL 3 have status `advies_uitgebracht`
+- **AND** the case detail MUST show a summary: "Adviezen: 2/3 ontvangen"
#### Scenario: Sequential consultation dependency
-- GIVEN consultation "Milieuonderzoek" must complete before consultation "Bodemadvies" can start
-- WHEN the admin configures consultation types for the case type
-- THEN they MUST be able to define dependencies between consultation types
-- AND "Bodemadvies" MUST NOT be createable until "Milieuonderzoek" has status `advies_uitgebracht`
+- **GIVEN** consultation "Milieuonderzoek" must complete before consultation "Bodemadvies" can start
+- **WHEN** the admin configures consultation types for the case type
+- **THEN** they MUST be able to define dependencies between consultation types
+- **AND** "Bodemadvies" MUST NOT be createable until "Milieuonderzoek" has status `advies_uitgebracht`
#### Scenario: Consultation summary view on case
-- GIVEN case `ZAAK-2026-000123` has 4 consultations (2 completed, 1 in progress, 1 not yet started)
-- WHEN viewing the case's "Adviezen" tab
-- THEN a summary bar MUST show: "2/4 adviezen ontvangen (1 in behandeling, 1 nog niet gestart)"
-- AND each consultation MUST be listed with: number, department, status, advice outcome (if completed), and deadline
-- AND a visual indicator MUST show the overall consultation progress
+- **GIVEN** case `ZAAK-2026-000123` has 4 consultations (2 completed, 1 in progress, 1 not yet started)
+- **WHEN** viewing the case's "Adviezen" tab
+- **THEN** a summary bar MUST show: "2/4 adviezen ontvangen (1 in behandeling, 1 nog niet gestart)"
+- **AND** each consultation MUST be listed with: number, department, status, advice outcome (if completed), and deadline
+- **AND** a visual indicator MUST show the overall consultation progress
## Non-Requirements
- This spec does NOT cover public participation / inspraak (citizen consultation on policy decisions) -- that is a different process
diff --git a/openspec/changes/dashboard/specs/dashboard/spec.md b/openspec/changes/dashboard/specs/dashboard/spec.md
deleted file mode 100644
index 6aa2c073..00000000
--- a/openspec/changes/dashboard/specs/dashboard/spec.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Delta: dashboard
-
-## Changes from base spec
-
-### REQ-DASH-003 (IMPLEMENTED)
-- Added Cases by Type horizontal bar chart widget to Dashboard.vue
-- Aggregates open cases by case type name, sorted by count descending
-- Click on bar navigates to Cases view filtered by type
-- Uses same CSS bar chart pattern as Cases by Status
-
-### Application widget registration (FIX)
-- Registered CasesOverviewWidget, MyTasksWidget, OverdueCasesWidget in Application.php
-- Fixed CasesOverviewWidget route from `.dashboard.index` to `.dashboard.page`
diff --git a/openspec/changes/document-zaakdossier/specs/document-zaakdossier/spec.md b/openspec/changes/document-zaakdossier/specs/document-zaakdossier/spec.md
index 811f5d3d..c760f546 100644
--- a/openspec/changes/document-zaakdossier/specs/document-zaakdossier/spec.md
+++ b/openspec/changes/document-zaakdossier/specs/document-zaakdossier/spec.md
@@ -11,8 +11,7 @@ Provide case dossier (zaakdossier) management within Procest, integrating docume
**Tender demand**: 80% of analyzed government tenders require document management in case dossiers. 65% specifically reference ZGW DRC compliance (informatieobjecten, vertrouwelijkheidaanduiding, document status lifecycle).
-## Requirements
-
+## ADDED Requirements
### Requirement: Register objects MUST support linked documents with ZGW informatieobject metadata
Objects MUST be able to reference one or more documents stored in Nextcloud Files. Each document link MUST carry ZGW DRC-compliant metadata fields stored as properties on an `informatieobject` schema within the register. The link between a zaak object and an informatieobject MUST follow the ZGW `zaakinformatieobject` pattern (a separate join entity with metadata about the relationship).
diff --git a/openspec/changes/dso-omgevingsloket/specs/dso-omgevingsloket/spec.md b/openspec/changes/dso-omgevingsloket/specs/dso-omgevingsloket/spec.md
index 224be7b2..07f82779 100644
--- a/openspec/changes/dso-omgevingsloket/specs/dso-omgevingsloket/spec.md
+++ b/openspec/changes/dso-omgevingsloket/specs/dso-omgevingsloket/spec.md
@@ -11,8 +11,7 @@ Provide VTH (Vergunningen, Toezicht, Handhaving) case management for DSO (Digita
**Tender demand**: 32% of analyzed government tenders require VTH (Vergunningen, Toezicht, Handhaving) capabilities aligned with the Omgevingswet/DSO. Municipalities need a register to store and query omgevingsvergunning data locally while maintaining compatibility with the national DSO-LV system. VTH-specific requirements appear in 20 of 69 procest-relevant tenders, with municipalities such as Zoetermeer (282155) and Westerkwartier (264852) specifying detailed DSO-LV integration requirements including triggerbericht ontvangst, verzoek ophalen, samenwerkfunctionaliteit, and beschikking generation.
-## Requirements
-
+## ADDED Requirements
### Requirement: REQ-DSO-001 -- Register schemas for core DSO entities
OpenRegister MUST provide register schemas for the core DSO entity types, enabling structured storage of omgevingsvergunning-related data. All schemas MUST be defined as OpenRegister schemas per ADR-001 (OpenRegister as Universal Data Layer) and MUST NOT use custom database tables. Schemas SHALL be registered during installation via repair steps or the `openregister:load-register` CLI command using the `dso_register.json` template.
diff --git a/openspec/changes/legesberekening/specs/legesberekening/spec.md b/openspec/changes/legesberekening/specs/legesberekening/spec.md
deleted file mode 100644
index 218a6b18..00000000
--- a/openspec/changes/legesberekening/specs/legesberekening/spec.md
+++ /dev/null
@@ -1,535 +0,0 @@
----
-status: implemented
----
-# Legesberekening Specification
-
-## Purpose
-
-Legesberekening is the rules engine that calculates municipal fees (leges) on permit cases. It applies the gemeentelijke legesverordening -- typically based on the VNG modellegesverordening -- to case attributes and produces a calculated amount. The module does NOT handle payment or invoicing; it calculates and exports to the financial system.
-
-**Tender demand**: Found as explicit requirement in 16 VTH tenders. Every VTH tender requires financial system export. Legesberekening is the #1 VTH-specific functional requirement after DSO integration.
-**Standards**: VNG Modellegesverordening, Unie van Waterschappen modelverordening (for waterschappen), StUF-FIN, GEMMA VTH-referentiecomponenten (VTH055-VTH057, VTH103, VTH117, VTH119)
-**Feature tier**: V1 (basic calculation, single verordening, manual export), V2 (multiple verordeningen, automatic DSO import, 4-ogen principe, versioned calculations, financial system connectors)
-
-**Competitive context**: Dimpact ZAC does not include built-in legesberekening -- municipalities typically use their financial system or a separate legesmodule. Flowable can model fee calculations via DMN decision tables, providing a standards-based approach. Procest should implement legesberekening as a PHP calculation service with verordening data stored in OpenRegister, making it fully integrated in the case workflow rather than requiring external tools.
-
-## Calculation Model
-
-### Fee Calculation Types
-
-| Type | Description | Example |
-|------|-------------|---------|
-| Vast bedrag | Fixed amount per application | Sloopmelding: EUR 250 |
-| Percentage | Percentage of bouwkosten | 2.4% of declared construction costs |
-| Staffel | Tiered brackets with different rates per bracket | 0-50K: 3%, 50K-250K: 2.5%, 250K+: 2% |
-| Maximum | Fee capped at a maximum amount | Leges max EUR 50,000 |
-| Minimum | Fee with a minimum floor amount | Leges min EUR 150 |
-| Combinatie | Multiple calculation types combined | Base fee + percentage + surcharge |
-| Staffel vast | Tiered brackets with fixed amounts per bracket | 0-50K: EUR 500, 50K-250K: EUR 1,200 |
-
-### Verordening Structure
-
-```
-Legesverordening (year, valid-from, valid-until)
-+-- Titel 1: Algemene dienstverlening
-| +-- Hoofdstuk 1: Burgerzaken
-| | +-- Artikel 1.1.1: Uittreksel GBA -- vast EUR 14,10
-| | +-- Artikel 1.1.2: Rijbewijs -- vast EUR 41,50
-| +-- Hoofdstuk 2: ...
-+-- Titel 2: Fysieke leefomgeving (Omgevingswet)
-| +-- Hoofdstuk 1: Omgevingsvergunning bouwactiviteit
-| | +-- Artikel 2.1.1: Bouwkosten t/m EUR 50.000 -- staffel 3,00%
-| | +-- Artikel 2.1.2: Bouwkosten EUR 50.001-250.000 -- staffel 2,50%
-| | +-- Artikel 2.1.3: Bouwkosten > EUR 250.000 -- staffel 2,00%
-| +-- Hoofdstuk 2: ...
-+-- Titel 3: Europese dienstenrichtlijn
-```
-
-### OpenRegister Schema Model
-
-```
-legesverordening:
- title: string # "Legesverordening 2026"
- year: integer # 2026
- validFrom: date # 2026-01-01
- validUntil: date # 2026-12-31
- status: enum # draft | active | archived
- municipality: string # gemeente identifier
-
-artikel:
- verordening: reference # -> legesverordening
- nummer: string # "2.1.1"
- titel: string # "Bouwkosten t/m EUR 50.000"
- hoofdstuk: string # "2.1"
- type: enum # vast | percentage | staffel | staffel_vast | maximum | minimum
- tarief: decimal # 3.00 (percentage or fixed amount)
- grondslag: string # "bouwkosten" (case property to calculate from)
- rangeMin: decimal # 0 (for staffel)
- rangeMax: decimal # 50000 (for staffel)
- maximumBedrag: decimal # null or cap amount
- minimumBedrag: decimal # null or floor amount
- caseTypes: array # applicable case type IDs
-
-berekening:
- case: reference # -> case
- verordening: reference # -> legesverordening
- status: enum # concept | ter_accordering | definitief | gecorrigeerd | terugbetaald
- totalAmount: decimal # 4750.00
- calculatedBy: string # user UID
- calculatedAt: datetime # timestamp
- approvedBy: string # user UID (4-ogen)
- approvedAt: datetime # timestamp
- version: integer # 1, 2, 3...
- reason: string # reason for correction/version
- lines: array # -> array of berekeningsregel
-
-berekeningsregel:
- artikel: reference # -> artikel
- grondslag: string # "bouwkosten"
- grondslagWaarde: decimal # 180000.00
- rangeApplied: string # "0 - 50000"
- tarief: decimal # 3.00
- bedrag: decimal # 1500.00
-```
-
-## Requirements
-
----
-
-### REQ-LEGES-01: Fee Calculation on Case Attributes
-
-The system MUST calculate leges based on case attributes (bouwkosten, activiteiten, oppervlakte) and the applicable legesverordening.
-
-**Feature tier**: V1
-
-
-#### Scenario LEGES-01a: Staffel (tiered) calculation
-
-- GIVEN a case "Omgevingsvergunning Bouw" with bouwkosten = EUR 180,000
-- AND legesverordening 2026 with artikel 2.1.1: bouwkosten t/m EUR 50,000 at 3.00% and artikel 2.1.2: EUR 50,001-250,000 at 2.50%
-- WHEN legesberekening is triggered via the case dashboard "Leges berekenen" button
-- THEN the system MUST calculate: (50,000 x 3.00%) + (130,000 x 2.50%) = EUR 1,500 + EUR 3,250 = EUR 4,750
-- AND the calculation MUST be stored as a `berekening` object in OpenRegister with berekeningsregels per artikel
-
-#### Scenario LEGES-01b: Fixed amount calculation
-
-- GIVEN a case "Sloopmelding" matching artikel 3.2.1: vast bedrag EUR 250
-- WHEN legesberekening is triggered
-- THEN the system MUST return EUR 250 with reference to artikel 3.2.1
-- AND a single berekeningsregel MUST be created with type "vast"
-
-#### Scenario LEGES-01c: Corrected construction costs
-
-- GIVEN a case with declared bouwkosten = EUR 300,000
-- AND the behandelaar corrects bouwkosten to EUR 220,000 (gecorrigeerde bouwsom)
-- WHEN legesberekening is recalculated
-- THEN the system MUST use the corrected amount EUR 220,000
-- AND the calculation history MUST show both the original and corrected calculation as separate versions
-
-#### Scenario LEGES-01d: Percentage calculation
-
-- GIVEN a case with bouwkosten = EUR 500,000
-- AND artikel 2.5.1: percentage 2.4% of bouwkosten
-- WHEN legesberekening is triggered
-- THEN the system MUST calculate: 500,000 x 2.4% = EUR 12,000
-
-#### Scenario LEGES-01e: Maximum cap
-
-- GIVEN a case with bouwkosten = EUR 5,000,000
-- AND the staffel calculation yields EUR 125,000
-- AND the verordening has a maximum cap of EUR 50,000
-- WHEN legesberekening is triggered
-- THEN the system MUST cap the amount at EUR 50,000
-- AND the berekeningsregel MUST show: "Berekend bedrag: EUR 125.000, gemaximeerd op EUR 50.000"
-
----
-
-### REQ-LEGES-02: Multiple Verordeningen Per Year
-
-The system MUST support multiple legesverordeningen active in the same year (e.g., when rates change mid-year).
-
-**Feature tier**: V2
-
-
-#### Scenario LEGES-02a: Select correct verordening by date
-
-- GIVEN legesverordening 2026-A valid from 2026-01-01 to 2026-06-30
-- AND legesverordening 2026-B valid from 2026-07-01 to 2026-12-31
-- AND a case with startdatum = 2026-08-15
-- WHEN legesberekening is triggered
-- THEN the system MUST apply verordening 2026-B (active on the case start date)
-
-#### Scenario LEGES-02b: No verordening found
-
-- GIVEN no active verordening exists for the case's start date
-- WHEN legesberekening is triggered
-- THEN the system MUST display an error: "Geen actieve legesverordening gevonden voor datum [startdatum]. Neem contact op met de beheerder."
-- AND the calculation MUST NOT proceed
-
-#### Scenario LEGES-02c: Transitional cases
-
-- GIVEN a case started on 2026-06-28 (under verordening 2026-A)
-- AND verordening 2026-B takes effect on 2026-07-01
-- WHEN legesberekening is triggered on 2026-07-05
-- THEN the system MUST use verordening 2026-A (based on case start date, not calculation date)
-
----
-
-### REQ-LEGES-03: Verrekening, Teruggaaf, and Corrections
-
-The system MUST support deducting previously imposed fees, issuing refunds, and correcting calculations.
-
-**Feature tier**: V1
-
-
-#### Scenario LEGES-03a: Deduct previously imposed leges
-
-- GIVEN a case where leges of EUR 4,750 were previously imposed for a provisional permit
-- AND the definitive permit has leges of EUR 6,200
-- WHEN the behandelaar applies verrekening
-- THEN the system MUST calculate the remaining amount: EUR 6,200 - EUR 4,750 = EUR 1,450
-- AND the export MUST show the net amount EUR 1,450 with reference to the original assessment
-
-#### Scenario LEGES-03b: Refund on withdrawn application (teruggaaf)
-
-- GIVEN a case with imposed leges of EUR 4,750
-- AND the aanvrager withdraws the application before the besluit
-- WHEN the behandelaar initiates teruggaaf
-- THEN the system MUST generate a negative amount (EUR -4,750 or partial refund per verordening)
-- AND the refund MUST be traceable in the calculation history
-- AND the refund percentage MUST be configurable (some verordeningen allow only 75% refund)
-
-#### Scenario LEGES-03c: Correction with audit trail
-
-- GIVEN a legesberekening with an error (wrong artikel applied)
-- WHEN the behandelaar corrects the calculation
-- THEN the original calculation MUST be preserved (not overwritten)
-- AND the correction MUST be a new version with: reason, corrected by, timestamp
-- AND the net difference MUST be exported to the financial system
-
-#### Scenario LEGES-03d: Multiple corrections
-
-- GIVEN a case with 3 calculation versions: initial (EUR 4,750), correction (EUR 5,200), refund (EUR -2,600)
-- WHEN viewing the leges panel on the case dashboard
-- THEN all 3 versions MUST be visible with version numbers (v1, v2, v3)
-- AND the net result (EUR 2,600) MUST be clearly displayed as the current effective amount
-
----
-
-### REQ-LEGES-04: 4-Ogen Principe (Four-Eyes Approval)
-
-The system MUST support requiring approval from a second person before a legesberekening becomes definitive.
-
-**Feature tier**: V2
-
-
-#### Scenario LEGES-04a: Require second approval
-
-- GIVEN a legesberekening of EUR 12,500 on case "2026-089"
-- AND the case type requires 4-ogen principe for leges above EUR 5,000
-- WHEN the behandelaar submits the calculation
-- THEN the status MUST be set to "Ter accordering"
-- AND a task MUST be created for the configured approver (teamleider or financieel medewerker)
-- AND the leges MUST NOT be exported until approved
-
-#### Scenario LEGES-04b: Approve legesberekening
-
-- GIVEN a pending legesberekening "Ter accordering"
-- WHEN the approver reviews and approves the calculation
-- THEN the status MUST change to "Definitief"
-- AND the audit trail MUST record: calculated by, approved by, timestamps
-- AND the calculation MUST now be eligible for export
-
-#### Scenario LEGES-04c: Reject legesberekening
-
-- GIVEN a pending legesberekening "Ter accordering"
-- WHEN the approver rejects the calculation with reason "Verkeerd tarief toegepast"
-- THEN the status MUST change to "Afgekeurd"
-- AND the behandelaar MUST receive a notification with the rejection reason
-- AND the behandelaar MUST be able to create a corrected version
-
-#### Scenario LEGES-04d: Threshold configuration
-
-- GIVEN the beheerder configures 4-ogen thresholds per case type
-- WHEN setting threshold to EUR 5,000 for "Omgevingsvergunning"
-- THEN calculations below EUR 5,000 MUST proceed directly to "Definitief"
-- AND calculations at or above EUR 5,000 MUST require approval
-
----
-
-### REQ-LEGES-05: Export to Financial System
-
-The system MUST support exporting legesberekeningen to the municipality's financial system. Export is always to an external system -- Procest does NOT handle payment or invoicing.
-
-**Feature tier**: V1
-
-
-#### Scenario LEGES-05a: Generate export file
-
-- GIVEN 5 definitieve legesberekeningen ready for export
-- WHEN the beheerder triggers a periodic export via the leges admin panel
-- THEN the system MUST generate an export containing per record: NAW-gegevens, BSN/KvK debiteur, zaaknummer, leges artikelnummer, omschrijving, bedrag, datum beschikking
-- AND the export format MUST be configurable: ASCII (flat file), XML, CSV, or StUF-FIN
-
-#### Scenario LEGES-05b: API export to Key2Financien
-
-- GIVEN an OpenConnector adapter configured for Key2Financien (Centric)
-- WHEN a legesberekening is marked definitief
-- THEN the system MUST support automatic push via StUF-FIN or REST API
-- AND the financial system reference number MUST be stored back on the berekening object
-- AND the export status MUST be tracked: "Te exporteren", "Geexporteerd", "Fout bij export"
-
-#### Scenario LEGES-05c: Supported export targets
-
-- The system MUST support export to common financial systems via configurable adapters:
- - Key2Financien (Centric) -- StUF-FIN or export file
- - Civision Innen (PinkRoccade) -- Centraal Facturen koppelvlak
- - iFinancieen (Centric) -- Export/API
- - Unit4Financials -- ZGW-API
- - Generic CSV/ASCII for other systems
-
-#### Scenario LEGES-05d: Export batch management
-
-- GIVEN the beheerder opens the export management screen
-- THEN the system MUST show: pending exports count, last export date, export history
-- AND each export batch MUST be downloadable as a file
-- AND failed exports MUST be retryable individually
-
----
-
-### REQ-LEGES-06: Verordening Administration
-
-The system MUST support administering legesverordeningen so that fee calculations stay current.
-
-**Feature tier**: V1
-
-
-#### Scenario LEGES-06a: Import verordening from Excel
-
-- GIVEN a new legesverordening 2027 prepared in Excel format with columns: artikelnummer, titel, type (vast/percentage/staffel), tarief, grondslag, range_min, range_max, maximum, minimum
-- WHEN the beheerder imports the Excel file via the admin panel
-- THEN the system MUST parse artikelen, tarieven, grondslagen, and staffels
-- AND the verordening MUST be created in draft status for review before activation
-- AND import errors MUST be reported per row: "Rij 15: ongeldig tarief '3,00%' -- gebruik decimaal getal (3.00)"
-
-#### Scenario LEGES-06b: Test verordening before production
-
-- GIVEN a draft legesverordening 2027
-- WHEN the beheerder runs a test calculation on a sample case
-- THEN the system MUST show the calculated amount using the draft verordening
-- AND the test MUST NOT affect the actual case or produce exportable records
-- AND the test result MUST show a comparison with the active verordening (if available)
-
-#### Scenario LEGES-06c: Activate verordening
-
-- GIVEN a draft verordening "2027" that has been reviewed
-- WHEN the beheerder clicks "Activeren"
-- THEN the verordening status MUST change from "draft" to "active"
-- AND any previously active verordening for the same date range MUST be archived
-- AND a confirmation dialog MUST warn: "Dit activeert de verordening voor alle nieuwe berekeningen vanaf [validFrom]"
-
-#### Scenario LEGES-06d: Manual artikel editing
-
-- GIVEN an active verordening
-- WHEN the beheerder needs to correct a tarief (e.g., typo: 2.50% should be 2.55%)
-- THEN the system MUST allow editing individual artikelen
-- AND the edit MUST be logged in the audit trail: "Artikel 2.1.2 tarief gewijzigd van 2.50% naar 2.55% door [beheerder]"
-- AND existing calculations MUST NOT be retroactively recalculated (only new calculations use the updated tarief)
-
----
-
-### REQ-LEGES-07: Calculation Version History
-
-The system MUST maintain a complete version history of all calculations per case, supporting accountantscontrole (audit by external accountant) and rechtmatigheidsverantwoording.
-
-**Feature tier**: V2
-
-
-#### Scenario LEGES-07a: Version history for accountability
-
-- GIVEN a case with 3 calculation versions: initial (EUR 4,750), correction (EUR 5,200), refund (EUR -2,600)
-- WHEN an accountant reviews the case
-- THEN all 3 versions MUST be visible with: timestamp, calculated by, approved by (if 4-ogen), reason for change
-- AND the net result (EUR 2,600) MUST be clearly shown
-
-#### Scenario LEGES-07b: Export version history as PDF
-
-- GIVEN a case with multiple calculation versions
-- WHEN the beheerder clicks "Exporteer berekening"
-- THEN the system MUST generate a PDF containing: verordening reference, all berekeningsregels per version, totals, audit information
-- AND the PDF MUST be suitable for archiving under the Archiefwet
-
-#### Scenario LEGES-07c: Immutable history
-
-- GIVEN a definitief legesberekening (version 1)
-- WHEN a correction is needed
-- THEN the system MUST NOT modify version 1
-- AND a new version 2 MUST be created with the corrected values
-- AND version 1 MUST remain accessible and unmodified
-
----
-
-### REQ-LEGES-08: Case Dashboard Integration
-
-The legesberekening MUST be accessible from the case dashboard as a dedicated panel.
-
-**Feature tier**: V1
-
-
-#### Scenario LEGES-08a: Leges panel on case dashboard
-
-- GIVEN a case of type "Omgevingsvergunning" (which has legesberekening enabled)
-- WHEN the behandelaar views the case dashboard
-- THEN a "Leges" panel MUST be displayed showing:
- - Current effective amount (or "Niet berekend" if no calculation exists)
- - Status (concept/ter_accordering/definitief)
- - Button "Leges berekenen" (if no calculation) or "Herberekenen" (if calculation exists)
-
-#### Scenario LEGES-08b: Calculation breakdown in panel
-
-- GIVEN a definitief legesberekening of EUR 4,750
-- WHEN the behandelaar expands the leges panel
-- THEN the breakdown MUST show per berekeningsregel: artikel nummer, omschrijving, grondslag, tarief, bedrag
-- AND the total MUST be shown at the bottom with EUR 4,750
-
-#### Scenario LEGES-08c: Trigger calculation from dashboard
-
-- GIVEN a case with bouwkosten property filled in
-- WHEN the behandelaar clicks "Leges berekenen"
-- THEN the system MUST fetch the applicable verordening
-- AND calculate the leges using the calculation service
-- AND display the result in the leges panel immediately
-- AND store the berekening in OpenRegister
-
-#### Scenario LEGES-08d: Case type without leges
-
-- GIVEN a case type "Klacht" that has no legesberekening configured
-- WHEN viewing the case dashboard
-- THEN the leges panel MUST NOT be rendered
-
----
-
-### REQ-LEGES-09: Samenloop (Combined Activities)
-
-The system MUST handle samenloop (combined activities) where a single case has multiple activities each with their own fee calculation, and specific samenloopregels determine the total.
-
-**Feature tier**: V1
-
-
-#### Scenario LEGES-09a: Multiple activities with individual fees
-
-- GIVEN a case with activities: "Bouwen" (leges EUR 4,750), "Kappen" (leges EUR 150), "Uitrit" (leges EUR 350)
-- WHEN legesberekening is triggered
-- THEN the system MUST calculate each activity's leges separately
-- AND the total MUST be the sum: EUR 4,750 + EUR 150 + EUR 350 = EUR 5,250
-
-#### Scenario LEGES-09b: Samenloop discount
-
-- GIVEN a verordening with samenloopkorting: "Bij 3 of meer activiteiten: 10% korting op het totaal"
-- AND a case with 3 activities totaling EUR 5,250
-- WHEN legesberekening applies samenloopregels
-- THEN the discount MUST be calculated: 10% x EUR 5,250 = EUR 525
-- AND the final amount MUST be: EUR 5,250 - EUR 525 = EUR 4,725
-
-#### Scenario LEGES-09c: Activity-specific rules
-
-- GIVEN activity "Bouwen" has a separate staffel calculation based on bouwkosten
-- AND activity "Kappen" has a fixed fee of EUR 75 per boom
-- AND the case specifies 2 bomen to be kapped
-- WHEN calculating the "Kappen" fee
-- THEN the system MUST calculate: 2 x EUR 75 = EUR 150
-
----
-
-### REQ-LEGES-10: Rounding and Precision
-
-The system MUST apply consistent rounding rules to all calculations.
-
-**Feature tier**: V1
-
-
-#### Scenario LEGES-10a: Standard rounding
-
-- GIVEN a staffel calculation yielding EUR 4,749.50
-- WHEN rounding is applied
-- THEN the system MUST round to the nearest whole euro: EUR 4,750 (per VNG modelverordening)
-
-#### Scenario LEGES-10b: Intermediate calculations
-
-- GIVEN a multi-bracket staffel calculation
-- WHEN calculating per bracket
-- THEN intermediate results MUST use full precision (no rounding per bracket)
-- AND rounding MUST only be applied to the final total
-
-#### Scenario LEGES-10c: Minimum fee
-
-- GIVEN a percentage calculation yielding EUR 12.50
-- AND the minimum fee for this artikel is EUR 150
-- WHEN the calculation completes
-- THEN the system MUST apply the minimum: EUR 150
-
-## Dependencies
-
-- **Case Management spec** (`../case-management/spec.md`): Leges are calculated on cases.
-- **VTH Module spec** (`../vth-module/spec.md`): Legesberekening is triggered during VTH permit workflow.
-- **Zaak Intake Flow spec** (`../zaak-intake-flow/spec.md`): Bouwkosten imported from DSO intake.
-- **Case Dashboard View spec** (`../case-dashboard-view/spec.md`): Leges panel on case detail.
-- **OpenRegister**: Verordeningen, artikelen, and calculations stored as OpenRegister objects.
-- **OpenConnector**: Financial system export adapters (StUF-FIN, Key2Financien, Civision Innen).
-- **BAG mock register**: Oppervlakte data for fee calculations based on floor area.
-
-### Using Mock Register Data
-
-This spec depends on the **BAG** mock register for oppervlakte (floor area) data used in fee calculations.
-
-**Loading the register:**
-```bash
-# Load BAG register (32 addresses + 21 objects + 21 buildings, register slug: "bag", schemas: "nummeraanduiding", "verblijfsobject", "pand")
-docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/bag_register.json
-```
-
-**Test data for this spec's use cases:**
-- **Oppervlakte for fee calculation**: BAG `verblijfsobject` records include `oppervlakte` (floor area in m2) -- use these values in staffel calculations
-- **Bouwkosten linked to BAG-object**: Link a BAG address to a permit case, then test fee calculation using the declared bouwkosten
-- **Multiple gebruiksdoel types**: BAG records include woonfunctie, kantoorfunctie, winkelfunctie -- test different fee rates per building type
-
-### Current Implementation Status
-
-**Not yet implemented.** No legesberekening-related schemas, controllers, services, or Vue components exist in the Procest codebase. There are no schemas for legesverordening, artikelen, tarieven, or berekeningen in `procest_register.json`.
-
-**Foundation available:**
-- Case properties infrastructure (`case_property_schema` in `SettingsService::SLUG_TO_CONFIG_KEY`) could store calculated leges amounts as case properties (e.g., `bouwkosten`, `legesbedrag`).
-- Property definitions (`property_definition_schema`) could define case type-specific fee-relevant fields.
-- The object store with `auditTrailsPlugin` provides version tracking for calculation history.
-- OpenConnector (external dependency) could host financial system export adapters.
-- The case detail view (`CaseDetail.vue`) could display a "Leges" panel using the existing `CnDetailCard` component pattern.
-- Task management infrastructure could be used for 4-ogen approval tasks.
-- `BrcController.php` demonstrates the ZGW Besluiten API pattern that could be extended for leges export notifications.
-
-**Partial implementations:** None.
-
-### Standards & References
-
-- **VNG Modellegesverordening**: Standard fee ordinance template used by most Dutch municipalities; defines the tariff structure (titels, hoofdstukken, artikelen). Procest follows this structure in the OpenRegister schema model.
-- **StUF-FIN**: XML-based standard for financial system integration in Dutch government.
-- **GEMMA VTH-referentiecomponenten**: VTH055 (Legesberekening), VTH056 (Legesnota), VTH057 (Financiele afhandeling), VTH103, VTH117, VTH119.
-- **Unie van Waterschappen Modelverordening**: Fee ordinance template for waterschappen.
-- **Rechtmatigheidsverantwoording**: Dutch government accountability framework requiring transparent fee calculations with full version history.
-- **Archiefwet**: Calculation records must be retained per archival requirements. PDF export supports this.
-- **Key2Financien / Civision Innen / Unit4Financials / iFinancieen**: Common Dutch municipal financial systems targeted for export.
-- **DMN 1.3**: Flowable uses DMN decision tables for fee calculations; Procest implements equivalent logic in PHP but could expose DMN-compatible rule definitions in the future.
-
-### Specificity Assessment
-
-This is a well-specified domain spec with concrete calculation examples, a clear verordening structure model, and defined OpenRegister schemas.
-
-**Strengths:** Clear calculation type taxonomy (vast, percentage, staffel, maximum, minimum, combinatie, staffel_vast). Concrete arithmetic examples with exact amounts. Verordening hierarchy diagram. Financial system export targets listed. OpenRegister schema model defined. Samenloop rules specified. Rounding rules defined.
-
-**Resolved ambiguities:**
-- Calculation engine is implemented as a PHP service (not n8n workflow), for precision and auditability.
-- Verordeningen use date-based activation with validFrom/validUntil fields.
-- The spec now supports per-article exemptions via the `caseTypes` field on artikelen.
-- Mid-year verordening changes are handled by case start date matching (REQ-LEGES-02c).
-- Calculation precision uses full decimal precision with rounding only on final totals (REQ-LEGES-10).
-- Excel import format is specified with column definitions (REQ-LEGES-06a).
-- 4-ogen threshold is configurable per case type (REQ-LEGES-04d).
diff --git a/openspec/changes/method-decomposition/specs/method-decomposition/spec.md b/openspec/changes/method-decomposition/specs/method-decomposition/spec.md
index 8605b84a..bd258472 100644
--- a/openspec/changes/method-decomposition/specs/method-decomposition/spec.md
+++ b/openspec/changes/method-decomposition/specs/method-decomposition/spec.md
@@ -24,382 +24,370 @@ Eliminate 152 PHPMD complexity suppressions by decomposing complex methods into
- **CouplingBetweenObjects suppressions:** 14 (too many dependencies)
- **TooManyMethods suppressions:** 8
-## Requirements
-
+## ADDED Requirements
---
-### REQ-DECOMP-01: ZrcController Decomposition
+### Requirement: REQ-DECOMP-01 — ZrcController Decomposition
The `ZrcController` (22 suppressions) MUST be decomposed into focused handler classes while preserving its public REST API surface. The controller handles ZGW Zaken CRUD operations for zaken, zaakobjecten, rollen, resultaten, statussen, and klantcontacten.
**Feature tier**: V1
+#### Scenario: Extract zaakobject creation handler
-#### Scenario DECOMP-01a: Extract zaakobject creation handler
-
-- GIVEN `ZrcController::createZaakObject()` has CyclomaticComplexity and NPathComplexity suppressions
-- WHEN the method is decomposed
-- THEN a `ZrcController/ZaakObjectHandler.php` class MUST be created
-- AND the handler MUST contain: `validateZaakObjectInput()`, `resolveZaakReference()`, `createZaakObject()`
-- AND `ZrcController::createZaakObject()` MUST delegate to the handler
-- AND the CyclomaticComplexity of each new method MUST be <=10
-- AND `phpunit --filter ZrcControllerTest` MUST pass with identical results
+- **GIVEN** `ZrcController::createZaakObject()` has CyclomaticComplexity and NPathComplexity suppressions
+- **WHEN** the method is decomposed
+- **THEN** a `ZrcController/ZaakObjectHandler.php` class MUST be created
+- **AND** the handler MUST contain: `validateZaakObjectInput()`, `resolveZaakReference()`, `createZaakObject()`
+- **AND** `ZrcController::createZaakObject()` MUST delegate to the handler
+- **AND** the CyclomaticComplexity of each new method MUST be <=10
+- **AND** `phpunit --filter ZrcControllerTest` MUST pass with identical results
-#### Scenario DECOMP-01b: Extract rol creation handler
+#### Scenario: Extract rol creation handler
-- GIVEN `ZrcController::createRol()` has CyclomaticComplexity and NPathComplexity suppressions
-- WHEN the method is decomposed
-- THEN a `ZrcController/RolHandler.php` class MUST be created
-- AND the handler MUST contain: `validateRolInput()`, `resolveRolType()`, `resolvePersonReference()`, `createRol()`
-- AND each new method MUST have NPathComplexity <=200
-- AND the public API response format MUST remain unchanged
+- **GIVEN** `ZrcController::createRol()` has CyclomaticComplexity and NPathComplexity suppressions
+- **WHEN** the method is decomposed
+- **THEN** a `ZrcController/RolHandler.php` class MUST be created
+- **AND** the handler MUST contain: `validateRolInput()`, `resolveRolType()`, `resolvePersonReference()`, `createRol()`
+- **AND** each new method MUST have NPathComplexity <=200
+- **AND** the public API response format MUST remain unchanged
-#### Scenario DECOMP-01c: Extract status creation handler
+#### Scenario: Extract status creation handler
-- GIVEN `ZrcController::createStatus()` has CC, NPath, and MethodLength suppressions (the most complex method)
-- WHEN the method is decomposed
-- THEN a `ZrcController/StatusHandler.php` class MUST be created
-- AND the handler MUST contain: `validateStatusInput()`, `resolveStatusType()`, `checkStatusTransition()`, `applyStatusEffects()`, `createStatusResponse()`
-- AND each new method MUST be <=50 lines
-- AND the status transition validation logic MUST be preserved exactly
+- **GIVEN** `ZrcController::createStatus()` has CC, NPath, and MethodLength suppressions (the most complex method)
+- **WHEN** the method is decomposed
+- **THEN** a `ZrcController/StatusHandler.php` class MUST be created
+- **AND** the handler MUST contain: `validateStatusInput()`, `resolveStatusType()`, `checkStatusTransition()`, `applyStatusEffects()`, `createStatusResponse()`
+- **AND** each new method MUST be <=50 lines
+- **AND** the status transition validation logic MUST be preserved exactly
-#### Scenario DECOMP-01d: Extract zaak update handler
+#### Scenario: Extract zaak update handler
-- GIVEN `ZrcController::updateZaak()` has CC, NPath, and MethodLength suppressions
-- WHEN the method is decomposed
-- THEN validation, field mapping, and response building MUST be separate methods
-- AND immutability checks (closed case protection) MUST be in a dedicated `validateZaakMutability()` method
+- **GIVEN** `ZrcController::updateZaak()` has CC, NPath, and MethodLength suppressions
+- **WHEN** the method is decomposed
+- **THEN** validation, field mapping, and response building MUST be separate methods
+- **AND** immutability checks (closed case protection) MUST be in a dedicated `validateZaakMutability()` method
-#### Scenario DECOMP-01e: Reduce class-level suppressions
+#### Scenario: Reduce class-level suppressions
-- GIVEN `ZrcController` has 7 class-level suppressions (coupling, class length, class complexity, method length, CC, NPath, TooManyMethods)
-- WHEN all method-level decompositions are complete
-- THEN the class MUST inject handler classes via constructor: `ZaakObjectHandler`, `RolHandler`, `StatusHandler`, `ResultaatHandler`
-- AND the controller class MUST be <=500 lines (excluding doc blocks)
-- AND the CouplingBetweenObjects count MUST be <=13 (the handler classes replace direct dependencies)
+- **GIVEN** `ZrcController` has 7 class-level suppressions (coupling, class length, class complexity, method length, CC, NPath, TooManyMethods)
+- **WHEN** all method-level decompositions are complete
+- **THEN** the class MUST inject handler classes via constructor: `ZaakObjectHandler`, `RolHandler`, `StatusHandler`, `ResultaatHandler`
+- **AND** the controller class MUST be <=500 lines (excluding doc blocks)
+- **AND** the CouplingBetweenObjects count MUST be <=13 (the handler classes replace direct dependencies)
---
-### REQ-DECOMP-02: ZgwService Decomposition
+### Requirement: REQ-DECOMP-02 — ZgwService Decomposition
The `ZgwService` (19 suppressions) MUST be decomposed. It is the core ZGW orchestration service handling zaak creation, JWT validation, sub-resource lookups, and API proxying.
**Feature tier**: V1
+#### Scenario: Extract JWT validation
-#### Scenario DECOMP-02a: Extract JWT validation
-
-- GIVEN `ZgwService::validateJwtToken()` and `validateJwtSignature()` have CC and NPath suppressions
-- WHEN the methods are decomposed
-- THEN a `Service/JwtValidationService.php` class MUST be created
-- AND it MUST contain: `validateTokenStructure()`, `validateTokenExpiry()`, `validateSignature()`, `extractClaims()`
-- AND each method MUST have CC <=10
+- **GIVEN** `ZgwService::validateJwtToken()` and `validateJwtSignature()` have CC and NPath suppressions
+- **WHEN** the methods are decomposed
+- **THEN** a `Service/JwtValidationService.php` class MUST be created
+- **AND** it MUST contain: `validateTokenStructure()`, `validateTokenExpiry()`, `validateSignature()`, `extractClaims()`
+- **AND** each method MUST have CC <=10
-#### Scenario DECOMP-02b: Extract sub-resource lookup methods
+#### Scenario: Extract sub-resource lookup methods
-- GIVEN `lookupZaakObjecten`, `lookupRollen`, `lookupStatussen`, `lookupResultaten` each have CC+NPath suppressions
-- WHEN the methods are decomposed
-- THEN a `Service/ZgwSubResourceResolver.php` class MUST be created
-- AND a generic `resolveSubResources(string $type, array $filters): array` method MUST handle the common pattern
-- AND type-specific logic MUST be in `resolve{Type}()` methods
-- AND the 4 individual lookup methods MUST delegate to the resolver
+- **GIVEN** `lookupZaakObjecten`, `lookupRollen`, `lookupStatussen`, `lookupResultaten` each have CC+NPath suppressions
+- **WHEN** the methods are decomposed
+- **THEN** a `Service/ZgwSubResourceResolver.php` class MUST be created
+- **AND** a generic `resolveSubResources(string $type, array $filters): array` method MUST handle the common pattern
+- **AND** type-specific logic MUST be in `resolve{Type}()` methods
+- **AND** the 4 individual lookup methods MUST delegate to the resolver
-#### Scenario DECOMP-02c: Extract handleSubResourceList
+#### Scenario: Extract handleSubResourceList
-- GIVEN `ZgwService::handleSubResourceList()` has CC, NPath, and MethodLength suppressions
-- WHEN the method is decomposed
-- THEN it MUST be split into: `parseListFilters()`, `querySubResources()`, `paginateResults()`, `buildListResponse()`
-- AND the pagination logic MUST use `ZgwPaginationHelper` (already exists) for the common parts
+- **GIVEN** `ZgwService::handleSubResourceList()` has CC, NPath, and MethodLength suppressions
+- **WHEN** the method is decomposed
+- **THEN** it MUST be split into: `parseListFilters()`, `querySubResources()`, `paginateResults()`, `buildListResponse()`
+- **AND** the pagination logic MUST use `ZgwPaginationHelper` (already exists) for the common parts
-#### Scenario DECOMP-02d: Reduce class coupling
+#### Scenario: Reduce class coupling
-- GIVEN `ZgwService` has CouplingBetweenObjects suppression (>13 dependencies)
-- WHEN JWT validation and sub-resource resolution are extracted
-- THEN the constructor parameter count MUST decrease by at least 3
-- AND the remaining `ZgwService` MUST focus only on zaak creation and high-level orchestration
+- **GIVEN** `ZgwService` has CouplingBetweenObjects suppression (>13 dependencies)
+- **WHEN** JWT validation and sub-resource resolution are extracted
+- **THEN** the constructor parameter count MUST decrease by at least 3
+- **AND** the remaining `ZgwService` MUST focus only on zaak creation and high-level orchestration
---
-### REQ-DECOMP-03: ZgwZrcRulesService Decomposition
+### Requirement: REQ-DECOMP-03 — ZgwZrcRulesService Decomposition
The `ZgwZrcRulesService` (17 suppressions) MUST be decomposed. It handles ZGW Zaken business rules validation (the most complex validation service).
**Feature tier**: V1
+#### Scenario: Extract status transition validation
-#### Scenario DECOMP-03a: Extract status transition validation
+- **GIVEN** `validateStatusTransition()` has NPath suppression and `handleZaakStatusUpdate()` has CC+NPath+MethodLength
+- **WHEN** the methods are decomposed
+- **THEN** a `Service/StatusTransitionValidator.php` class MUST be created
+- **AND** it MUST contain: `validateTransitionAllowed()`, `validateRequiredProperties()`, `validateRequiredDocuments()`, `applyTransitionEffects()`
+- **AND** the transition validation matrix MUST be configurable (not hardcoded if/else chains)
-- GIVEN `validateStatusTransition()` has NPath suppression and `handleZaakStatusUpdate()` has CC+NPath+MethodLength
-- WHEN the methods are decomposed
-- THEN a `Service/StatusTransitionValidator.php` class MUST be created
-- AND it MUST contain: `validateTransitionAllowed()`, `validateRequiredProperties()`, `validateRequiredDocuments()`, `applyTransitionEffects()`
-- AND the transition validation matrix MUST be configurable (not hardcoded if/else chains)
+#### Scenario: Extract zaak creation validation
-#### Scenario DECOMP-03b: Extract zaak creation validation
+- **GIVEN** `validateCreateZaak()` has CC suppression
+- **WHEN** the method is decomposed
+- **THEN** it MUST be split into: `validateRequiredFields()`, `validateCaseTypeReference()`, `validateDateConsistency()`, `validateConfidentiality()`
+- **AND** each validator MUST throw a specific exception type for different validation failures
-- GIVEN `validateCreateZaak()` has CC suppression
-- WHEN the method is decomposed
-- THEN it MUST be split into: `validateRequiredFields()`, `validateCaseTypeReference()`, `validateDateConsistency()`, `validateConfidentiality()`
-- AND each validator MUST throw a specific exception type for different validation failures
+#### Scenario: Extract zaak update validation
-#### Scenario DECOMP-03c: Extract zaak update validation
+- **GIVEN** `validateZaakUpdate()` and `validateImmutability()` have CC+NPath suppressions
+- **WHEN** the methods are decomposed
+- **THEN** immutability rules MUST be in an `ImmutabilityChecker` with methods per rule: `checkClosedCase()`, `checkProtectedFields()`, `checkArchivalStatus()`
+- **AND** the update validator MUST use guard clauses (early returns) to reduce nesting
-- GIVEN `validateZaakUpdate()` and `validateImmutability()` have CC+NPath suppressions
-- WHEN the methods are decomposed
-- THEN immutability rules MUST be in an `ImmutabilityChecker` with methods per rule: `checkClosedCase()`, `checkProtectedFields()`, `checkArchivalStatus()`
-- AND the update validator MUST use guard clauses (early returns) to reduce nesting
+#### Scenario: Extract role validation
-#### Scenario DECOMP-03d: Extract role validation
-
-- GIVEN `validateRolCreate()` has CC+NPath suppressions
-- WHEN the method is decomposed
-- THEN it MUST be split into: `validateRolType()`, `validateBetrokkeneData()`, `validateUniqueRol()`
-- AND BSN validation, vestigingsnummer validation, and medewerker validation MUST be separate methods
+- **GIVEN** `validateRolCreate()` has CC+NPath suppressions
+- **WHEN** the method is decomposed
+- **THEN** it MUST be split into: `validateRolType()`, `validateBetrokkeneData()`, `validateUniqueRol()`
+- **AND** BSN validation, vestigingsnummer validation, and medewerker validation MUST be separate methods
---
-### REQ-DECOMP-04: ZgwZtcRulesService Decomposition
+### Requirement: REQ-DECOMP-04 — ZgwZtcRulesService Decomposition
The `ZgwZtcRulesService` (16 suppressions) MUST be decomposed. It handles ZGW Catalogi (zaaktype) business rules.
**Feature tier**: V1
+#### Scenario: Extract zaaktype validation
-#### Scenario DECOMP-04a: Extract zaaktype validation
-
-- GIVEN `validateZaaktypeCreate()` has CC+NPath suppressions
-- WHEN the method is decomposed
-- THEN a `Service/ZaaktypeValidator.php` class MUST be created
-- AND it MUST contain: `validateIdentificatie()`, `validateDateRange()`, `validateConcept()`, `validateRelatedTypes()`
-- AND each method MUST have CC <=10
+- **GIVEN** `validateZaaktypeCreate()` has CC+NPath suppressions
+- **WHEN** the method is decomposed
+- **THEN** a `Service/ZaaktypeValidator.php` class MUST be created
+- **AND** it MUST contain: `validateIdentificatie()`, `validateDateRange()`, `validateConcept()`, `validateRelatedTypes()`
+- **AND** each method MUST have CC <=10
-#### Scenario DECOMP-04b: Extract sub-type validation methods
+#### Scenario: Extract sub-type validation methods
-- GIVEN `validateStatusTypeCreate`, `validateResultaatTypeCreate`, `validateEigenschapCreate`, `validateInformatieObjectTypeCreate` each have CC+NPath
-- WHEN the methods are decomposed
-- THEN a common `validateSubTypeCreate(string $type, array $data, array $rules): void` pattern MUST be used
-- AND type-specific rules MUST be in dedicated validator methods
-- AND the validation rule structure MUST be declarative (array of rules) rather than procedural (if/else chains)
+- **GIVEN** `validateStatusTypeCreate`, `validateResultaatTypeCreate`, `validateEigenschapCreate`, `validateInformatieObjectTypeCreate` each have CC+NPath
+- **WHEN** the methods are decomposed
+- **THEN** a common `validateSubTypeCreate(string $type, array $data, array $rules): void` pattern MUST be used
+- **AND** type-specific rules MUST be in dedicated validator methods
+- **AND** the validation rule structure MUST be declarative (array of rules) rather than procedural (if/else chains)
-#### Scenario DECOMP-04c: Extract reference resolution
+#### Scenario: Extract reference resolution
-- GIVEN `resolveZaaktypeReference()` and `resolveNestedObjectReferences()` have CC+NPath suppressions
-- WHEN the methods are decomposed
-- THEN a `Service/ZgwReferenceResolver.php` class MUST be created (shared across all rules services)
-- AND it MUST handle: URL-based references, nested object resolution, and reference validation
-- AND circular reference detection MUST be included
+- **GIVEN** `resolveZaaktypeReference()` and `resolveNestedObjectReferences()` have CC+NPath suppressions
+- **WHEN** the methods are decomposed
+- **THEN** a `Service/ZgwReferenceResolver.php` class MUST be created (shared across all rules services)
+- **AND** it MUST handle: URL-based references, nested object resolution, and reference validation
+- **AND** circular reference detection MUST be included
---
-### REQ-DECOMP-05: ZtcController Decomposition
+### Requirement: REQ-DECOMP-05 — ZtcController Decomposition
The `ZtcController` (16 suppressions) MUST be decomposed following the same handler pattern as ZrcController.
**Feature tier**: V1
+#### Scenario: Extract informatie object type creation
-#### Scenario DECOMP-05a: Extract informatie object type creation
-
-- GIVEN `createInformatieObjectType()` has CC+NPath+MethodLength suppressions (most complex method)
-- WHEN the method is decomposed
-- THEN a `ZtcController/InformatieObjectTypeHandler.php` class MUST be created
-- AND it MUST contain: `validateInput()`, `resolveReferences()`, `create()`, `buildResponse()`
+- **GIVEN** `createInformatieObjectType()` has CC+NPath+MethodLength suppressions (most complex method)
+- **WHEN** the method is decomposed
+- **THEN** a `ZtcController/InformatieObjectTypeHandler.php` class MUST be created
+- **AND** it MUST contain: `validateInput()`, `resolveReferences()`, `create()`, `buildResponse()`
-#### Scenario DECOMP-05b: Extract listing methods
+#### Scenario: Extract listing methods
-- GIVEN `listCatalogi()` and `listZaaktypen()` each have CC+NPath suppressions
-- WHEN the methods are decomposed
-- THEN filter parsing, query building, and response formatting MUST be separate methods
-- AND a shared `buildListResponse()` pattern MUST be reusable across all listing endpoints
+- **GIVEN** `listCatalogi()` and `listZaaktypen()` each have CC+NPath suppressions
+- **WHEN** the methods are decomposed
+- **THEN** filter parsing, query building, and response formatting MUST be separate methods
+- **AND** a shared `buildListResponse()` pattern MUST be reusable across all listing endpoints
-#### Scenario DECOMP-05c: Extract sub-type creation handlers
+#### Scenario: Extract sub-type creation handlers
-- GIVEN `createStatusType()` and `createResultaatType()` have CC+NPath suppressions
-- WHEN the methods are decomposed
-- THEN `ZtcController/StatusTypeHandler.php` and `ZtcController/ResultaatTypeHandler.php` MUST be created
-- AND each handler MUST follow the same validate-resolve-create-respond pattern
+- **GIVEN** `createStatusType()` and `createResultaatType()` have CC+NPath suppressions
+- **WHEN** the methods are decomposed
+- **THEN** `ZtcController/StatusTypeHandler.php` and `ZtcController/ResultaatTypeHandler.php` MUST be created
+- **AND** each handler MUST follow the same validate-resolve-create-respond pattern
---
-### REQ-DECOMP-06: BrcController and ZgwBrcRulesService Decomposition
+### Requirement: REQ-DECOMP-06 — BrcController and ZgwBrcRulesService Decomposition
The `BrcController` (9 suppressions) and `ZgwBrcRulesService` (12 suppressions) MUST be decomposed.
**Feature tier**: V1
+#### Scenario: Extract besluit creation handler
-#### Scenario DECOMP-06a: Extract besluit creation handler
+- **GIVEN** `BrcController::createBesluit()` has CC+NPath+MethodLength suppressions
+- **WHEN** the method is decomposed
+- **THEN** a `BrcController/BesluitHandler.php` class MUST be created
+- **AND** it MUST contain: `validateBesluitInput()`, `resolveBesluitType()`, `resolveZaakReference()`, `createBesluit()`, `buildBesluitResponse()`
-- GIVEN `BrcController::createBesluit()` has CC+NPath+MethodLength suppressions
-- WHEN the method is decomposed
-- THEN a `BrcController/BesluitHandler.php` class MUST be created
-- AND it MUST contain: `validateBesluitInput()`, `resolveBesluitType()`, `resolveZaakReference()`, `createBesluit()`, `buildBesluitResponse()`
+#### Scenario: Extract besluit validation service
-#### Scenario DECOMP-06b: Extract besluit validation service
+- **GIVEN** `ZgwBrcRulesService` has `validateBesluitCreate`, `validateBesluitUpdate`, `validateBesluitInformatieObject` with multiple suppressions
+- **WHEN** the service is decomposed
+- **THEN** `validateBesluitCreate()` MUST be split into: `validateRequiredFields()`, `validateBesluitTypeReference()`, `validateZaakReference()`, `validateDateFields()`
+- **AND** `validateBesluitInformatieObject()` MUST use early returns to reduce NPath complexity
-- GIVEN `ZgwBrcRulesService` has `validateBesluitCreate`, `validateBesluitUpdate`, `validateBesluitInformatieObject` with multiple suppressions
-- WHEN the service is decomposed
-- THEN `validateBesluitCreate()` MUST be split into: `validateRequiredFields()`, `validateBesluitTypeReference()`, `validateZaakReference()`, `validateDateFields()`
-- AND `validateBesluitInformatieObject()` MUST use early returns to reduce NPath complexity
+#### Scenario: Extract search method
-#### Scenario DECOMP-06c: Extract search method
-
-- GIVEN `BrcController::searchBesluiten()` has CC suppression
-- WHEN the method is decomposed
-- THEN filter parsing MUST be extracted to a `parseSearchFilters()` method
-- AND the query building MUST use the shared pattern from REQ-DECOMP-05b
+- **GIVEN** `BrcController::searchBesluiten()` has CC suppression
+- **WHEN** the method is decomposed
+- **THEN** filter parsing MUST be extracted to a `parseSearchFilters()` method
+- **AND** the query building MUST use the shared pattern from REQ-DECOMP-05b
---
-### REQ-DECOMP-07: DrcController and ZgwDrcRulesService Decomposition
+### Requirement: REQ-DECOMP-07 — DrcController and ZgwDrcRulesService Decomposition
The `DrcController` (9 suppressions) and `ZgwDrcRulesService` (9 suppressions) MUST be decomposed.
**Feature tier**: V1
+#### Scenario: Extract document creation handler
-#### Scenario DECOMP-07a: Extract document creation handler
-
-- GIVEN `DrcController::createDocument()` has CC+NPath suppressions
-- WHEN the method is decomposed
-- THEN a `DrcController/DocumentHandler.php` class MUST be created
-- AND file upload handling, metadata validation, and response building MUST be separate methods
+- **GIVEN** `DrcController::createDocument()` has CC+NPath suppressions
+- **WHEN** the method is decomposed
+- **THEN** a `DrcController/DocumentHandler.php` class MUST be created
+- **AND** file upload handling, metadata validation, and response building MUST be separate methods
-#### Scenario DECOMP-07b: Extract document validation
+#### Scenario: Extract document validation
-- GIVEN `ZgwDrcRulesService` has `validateDocumentCreate`, `validateDocumentUpdate`, `validateCrossRegisterReferences` with suppressions
-- WHEN the service is decomposed
-- THEN each validation method MUST use the guard clause pattern (early returns)
-- AND file format validation, size validation, and metadata validation MUST be separate methods
+- **GIVEN** `ZgwDrcRulesService` has `validateDocumentCreate`, `validateDocumentUpdate`, `validateCrossRegisterReferences` with suppressions
+- **WHEN** the service is decomposed
+- **THEN** each validation method MUST use the guard clause pattern (early returns)
+- **AND** file format validation, size validation, and metadata validation MUST be separate methods
-#### Scenario DECOMP-07c: Extract cross-register reference validation
+#### Scenario: Extract cross-register reference validation
-- GIVEN `validateCrossRegisterReferences()` has CC suppression
-- WHEN the method is decomposed
-- THEN the `ZgwReferenceResolver` from REQ-DECOMP-04c MUST be reused
-- AND document-specific reference patterns (informatieobjecttype, zaak) MUST be separate resolver methods
+- **GIVEN** `validateCrossRegisterReferences()` has CC suppression
+- **WHEN** the method is decomposed
+- **THEN** the `ZgwReferenceResolver` from REQ-DECOMP-04c MUST be reused
+- **AND** document-specific reference patterns (informatieobjecttype, zaak) MUST be separate resolver methods
---
-### REQ-DECOMP-08: Shared Business Rules Decomposition
+### Requirement: REQ-DECOMP-08 — Shared Business Rules Decomposition
The `ZgwBusinessRulesService` (6 suppressions) and `ZgwRulesBase` (4 suppressions) MUST be decomposed.
**Feature tier**: V1
+#### Scenario: Extract pagination validation
-#### Scenario DECOMP-08a: Extract pagination validation
-
-- GIVEN `validatePagination()` has CC+NPath suppressions
-- WHEN the method is decomposed
-- THEN it MUST be split into: `validatePageNumber()`, `validatePageSize()`, `validateSortField()`, `buildPaginationResponse()`
-- AND `ZgwPaginationHelper` (already exists) SHOULD absorb the common validation logic
+- **GIVEN** `validatePagination()` has CC+NPath suppressions
+- **WHEN** the method is decomposed
+- **THEN** it MUST be split into: `validatePageNumber()`, `validatePageSize()`, `validateSortField()`, `buildPaginationResponse()`
+- **AND** `ZgwPaginationHelper` (already exists) SHOULD absorb the common validation logic
-#### Scenario DECOMP-08b: Extract date and URL field validation
+#### Scenario: Extract date and URL field validation
-- GIVEN `validateDateFields()` has CC+NPath and `validateUrlFields()` has CC suppressions
-- WHEN the methods are decomposed
-- THEN a `FieldValidator` utility MUST be created with: `validateDateFormat()`, `validateDateRange()`, `validateUrl()`, `validateUrlReachability()`
-- AND the validators MUST be reusable across all rules services
+- **GIVEN** `validateDateFields()` has CC+NPath and `validateUrlFields()` has CC suppressions
+- **WHEN** the methods are decomposed
+- **THEN** a `FieldValidator` utility MUST be created with: `validateDateFormat()`, `validateDateRange()`, `validateUrl()`, `validateUrlReachability()`
+- **AND** the validators MUST be reusable across all rules services
-#### Scenario DECOMP-08c: Reduce ZgwRulesBase coupling
+#### Scenario: Reduce ZgwRulesBase coupling
-- GIVEN `ZgwRulesBase` has CouplingBetweenObjects, ClassComplexity, TooManyMethods, and CC suppressions
-- WHEN the base class is decomposed
-- THEN helper methods MUST be moved to dedicated utility classes (`FieldValidator`, `ZgwReferenceResolver`)
-- AND the base class MUST contain only shared infrastructure (error handling, logging, common type resolution)
+- **GIVEN** `ZgwRulesBase` has CouplingBetweenObjects, ClassComplexity, TooManyMethods, and CC suppressions
+- **WHEN** the base class is decomposed
+- **THEN** helper methods MUST be moved to dedicated utility classes (`FieldValidator`, `ZgwReferenceResolver`)
+- **AND** the base class MUST contain only shared infrastructure (error handling, logging, common type resolution)
---
-### REQ-DECOMP-09: AcController Decomposition
+### Requirement: REQ-DECOMP-09 — AcController Decomposition
The `AcController` (5 suppressions) MUST be decomposed.
**Feature tier**: V2
+#### Scenario: Extract autorisatie creation handler
-#### Scenario DECOMP-09a: Extract autorisatie creation handler
+- **GIVEN** `AcController::createAutorisatie()` has CC+NPath suppressions
+- **WHEN** the method is decomposed
+- **THEN** a `AcController/AutorisatieHandler.php` class MUST be created
+- **AND** scope validation, client verification, and autorisatie creation MUST be separate methods
-- GIVEN `AcController::createAutorisatie()` has CC+NPath suppressions
-- WHEN the method is decomposed
-- THEN a `AcController/AutorisatieHandler.php` class MUST be created
-- AND scope validation, client verification, and autorisatie creation MUST be separate methods
+#### Scenario: Reduce class complexity
-#### Scenario DECOMP-09b: Reduce class complexity
+- **GIVEN** `AcController` has ClassComplexity, CC, and NPath class-level suppressions
+- **WHEN** the handler is extracted
+- **THEN** the controller MUST only contain route handlers that delegate to the handler class
+- **AND** the class complexity MUST drop below the PHPMD threshold
-- GIVEN `AcController` has ClassComplexity, CC, and NPath class-level suppressions
-- WHEN the handler is extracted
-- THEN the controller MUST only contain route handlers that delegate to the handler class
-- AND the class complexity MUST drop below the PHPMD threshold
+#### Scenario: Scope validation extraction
-#### Scenario DECOMP-09c: Scope validation extraction
-
-- GIVEN autorisatie scope validation involves checking multiple ZGW API scopes against permissions
-- WHEN the validation is extracted
-- THEN a `ScopeValidator` MUST check: scope format, scope existence, scope combination rules
-- AND unknown scopes MUST produce descriptive error messages
+- **GIVEN** autorisatie scope validation involves checking multiple ZGW API scopes against permissions
+- **WHEN** the validation is extracted
+- **THEN** a `ScopeValidator` MUST check: scope format, scope existence, scope combination rules
+- **AND** unknown scopes MUST produce descriptive error messages
---
-### REQ-DECOMP-10: LoadDefaultZgwMappings Decomposition
+### Requirement: REQ-DECOMP-10 — LoadDefaultZgwMappings Decomposition
The `LoadDefaultZgwMappings` repair step (4 suppressions) MUST be decomposed.
**Feature tier**: V2
+#### Scenario: Extract mapping loaders
-#### Scenario DECOMP-10a: Extract mapping loaders
-
-- GIVEN `LoadDefaultZgwMappings` has ClassLength, MethodLength (2x), and CC suppressions
-- WHEN the repair step is decomposed
-- THEN separate methods MUST be created for each mapping category: `loadZaakMappings()`, `loadDocumentMappings()`, `loadBesluitMappings()`, `loadCatalogiMappings()`
-- AND each method MUST be <=50 lines
+- **GIVEN** `LoadDefaultZgwMappings` has ClassLength, MethodLength (2x), and CC suppressions
+- **WHEN** the repair step is decomposed
+- **THEN** separate methods MUST be created for each mapping category: `loadZaakMappings()`, `loadDocumentMappings()`, `loadBesluitMappings()`, `loadCatalogiMappings()`
+- **AND** each method MUST be <=50 lines
-#### Scenario DECOMP-10b: Extract mapping data to configuration files
+#### Scenario: Extract mapping data to configuration files
-- GIVEN the repair step contains hardcoded mapping arrays that cause ExcessiveClassLength
-- WHEN the data is extracted
-- THEN mapping definitions MUST be moved to JSON configuration files in `lib/Settings/zgw_mappings/`
-- AND the repair step MUST load and parse these files instead of containing inline arrays
-- AND the class length MUST drop below 1000 lines
+- **GIVEN** the repair step contains hardcoded mapping arrays that cause ExcessiveClassLength
+- **WHEN** the data is extracted
+- **THEN** mapping definitions MUST be moved to JSON configuration files in `lib/Settings/zgw_mappings/`
+- **AND** the repair step MUST load and parse these files instead of containing inline arrays
+- **AND** the class length MUST drop below 1000 lines
-#### Scenario DECOMP-10c: Idempotent mapping updates
+#### Scenario: Idempotent mapping updates
-- GIVEN the repair step runs on every app update
-- WHEN mapping data is loaded from JSON files
-- THEN existing mappings MUST be updated (not duplicated) using a mapping identifier as the unique key
-- AND a version field in the JSON files MUST trigger re-import only when the version changes
+- **GIVEN** the repair step runs on every app update
+- **WHEN** mapping data is loaded from JSON files
+- **THEN** existing mappings MUST be updated (not duplicated) using a mapping identifier as the unique key
+- **AND** a version field in the JSON files MUST trigger re-import only when the version changes
---
-### REQ-DECOMP-11: Remaining Single-Suppression Files
+### Requirement: REQ-DECOMP-11 — Remaining Single-Suppression Files
Files with single suppressions MUST be addressed by reducing coupling or class length.
**Feature tier**: V2
+#### Scenario: ZgwMappingService class length reduction
-#### Scenario DECOMP-11a: ZgwMappingService class length reduction
-
-- GIVEN `ZgwMappingService` has ExcessiveClassLength suppression
-- WHEN the service is decomposed
-- THEN mapping transformation logic MUST be extracted to a `MappingTransformService`
-- AND the remaining service MUST be <=1000 lines
+- **GIVEN** `ZgwMappingService` has ExcessiveClassLength suppression
+- **WHEN** the service is decomposed
+- **THEN** mapping transformation logic MUST be extracted to a `MappingTransformService`
+- **AND** the remaining service MUST be <=1000 lines
-#### Scenario DECOMP-11b: Reduce coupling in utility services
+#### Scenario: Reduce coupling in utility services
-- GIVEN `ZgwDocumentService`, `NotificatieService`, and `ZgwAuthMiddleware` each have CouplingBetweenObjects suppressions
-- WHEN the services are refactored
-- THEN rarely-used dependencies MUST be lazy-loaded via `IServerContainer::get()`
+- **GIVEN** `ZgwDocumentService`, `NotificatieService`, and `ZgwAuthMiddleware` each have CouplingBetweenObjects suppressions
+- **WHEN** the services are refactored
+- **THEN** rarely-used dependencies MUST be lazy-loaded via `IServerContainer::get()`
- OR related dependencies MUST be grouped into a composite service
-#### Scenario DECOMP-11c: Verify no new violations
+#### Scenario: Verify no new violations
-- GIVEN all decompositions are complete
-- WHEN `composer check:strict` is run
-- THEN 0 PHPMD violations MUST be reported
-- AND 0 new PHPCS violations MUST be introduced
-- AND 0 new Psalm/PHPStan issues MUST be introduced
+- **GIVEN** all decompositions are complete
+- **WHEN** `composer check:strict` is run
+- **THEN** 0 PHPMD violations MUST be reported
+- **AND** 0 new PHPCS violations MUST be introduced
+- **AND** 0 new Psalm/PHPStan issues MUST be introduced
---
diff --git a/openspec/changes/mijn-overheid-integration/specs/mijn-overheid-integration/spec.md b/openspec/changes/mijn-overheid-integration/specs/mijn-overheid-integration/spec.md
deleted file mode 100644
index 086c8a5f..00000000
--- a/openspec/changes/mijn-overheid-integration/specs/mijn-overheid-integration/spec.md
+++ /dev/null
@@ -1,441 +0,0 @@
----
-status: implemented
----
-# mijn-overheid-integration Specification
-
-## Purpose
-Send official government messages to the national Mijn Overheid Berichtenbox from within Procest case context, and provide citizen portal integration for case status tracking. Mijn Overheid is the government-mandated channel for official citizen correspondence. Messages follow strict format requirements and support read tracking. This integration also covers DigiD-authenticated status page access and proactive case status push notifications.
-
-## Context
-Dutch municipalities are increasingly required to send official correspondence (beschikkingen, status updates, decision notifications) through the Mijn Overheid Berichtenbox rather than postal mail. The Wet digitale overheid (Wdo) mandates digital government communication channels. This integration enables Procest case workers to send messages directly from a case, with the message and any attachment stored as case documents for the audit trail. Beyond messaging, Mijn Overheid provides a status page where citizens can track their cases -- Procest pushes status updates to this page via the Zaakstatus API.
-
-## Requirements
-
-### Requirement 1: Send messages to Berichtenbox
-The system MUST support sending messages to a citizen's Mijn Overheid Berichtenbox from within a case context.
-
-#### Scenario 1.1: Send a simple text message
-- GIVEN a case `zaak-1` with a linked BSN (burgerservicenummer) on a role with type "Initiator"
-- WHEN the case worker clicks "Bericht verzenden via Mijn Overheid" in the case detail action menu
-- THEN a message composer dialog MUST appear with:
- - Subject field (pre-filled with case type name if configured)
- - Body text area (plain text only)
- - BSN display (read-only, from the case's initiator role)
- - Bericht type dropdown
- - Optional PDF attachment upload
-- AND upon sending, the system MUST call the Berichtenbox API with the message
-- AND the message MUST be stored as a case document in PDF format (generated by Docudesk)
-- AND the audit trail in `ActivityTimeline` MUST record: "Bericht verzonden via Mijn Overheid: [subject]"
-
-#### Scenario 1.2: Send message with PDF attachment
-- GIVEN a case with a linked BSN
-- WHEN the case worker attaches a single PDF document to the Berichtenbox message
-- THEN the system MUST validate the attachment:
- - File type MUST be PDF only
- - File size MUST NOT exceed 10 MB
- - Only one attachment per message (Mijn Overheid limitation)
-- AND the message with attachment MUST be sent via the Berichtenbox API
-- AND both the message text and the attachment MUST be stored in the case dossier
-
-#### Scenario 1.3: Reject message without BSN
-- GIVEN a case `zaak-1` without a linked BSN on any role
-- WHEN the case worker opens the "Bericht verzenden via Mijn Overheid" dialog
-- THEN the dialog MUST display an error: "BSN is verplicht voor berichten via Mijn Overheid. Koppel eerst een persoon met BSN aan deze zaak."
-- AND the send button MUST be disabled
-- AND a link to "Persoon toevoegen" MUST be shown
-
-#### Scenario 1.4: Send decision notification (beschikking)
-- GIVEN a WOO case in stage "Besluit" with an approved decision document
-- WHEN the case worker triggers "Beschikking verzenden via Mijn Overheid"
-- THEN the system MUST compose a message with:
- - Subject: "Besluit op uw WOO-verzoek [case identifier]"
- - Body: standard decision notification text (configurable template)
- - Attachment: the generated beschikking PDF
-- AND the case worker MUST be able to review and edit before sending
-
-#### Scenario 1.5: Batch message sending
-- GIVEN 15 cases of zaaktype "Vergunning" have reached status "Besluit"
-- WHEN the admin triggers "Batch verzenden via Mijn Overheid" from the case list view
-- THEN the system MUST:
- - Validate that all 15 cases have linked BSNs (report any without)
- - Generate messages using the configured template per zaaktype
- - Send messages sequentially via the Berichtenbox API (respecting rate limits)
- - Report results: 13 sent, 2 failed (BSN missing)
-- AND each sent message MUST be recorded on its respective case
-
-### Requirement 2: Bericht type codes for message categorization
-The system MUST support bericht type codes for message routing and categorization within Mijn Overheid.
-
-#### Scenario 2.1: Select bericht type on send
-- GIVEN a configured set of bericht type codes in the Procest settings
-- WHEN the case worker composes a Berichtenbox message
-- THEN a bericht type dropdown MUST be displayed with available codes and human-readable labels
-- AND the selected type code MUST be included in the API payload
-- AND the type code MUST be stored on the sent message record
-
-#### Scenario 2.2: Default bericht type per zaaktype
-- GIVEN zaaktype `omgevingsvergunning` has a configured default bericht type code "OMG-BESLUIT"
-- WHEN the case worker opens the message composer for a case of this type
-- THEN the bericht type MUST be pre-selected with "OMG-BESLUIT"
-- AND the case worker MUST be able to override the default
-
-#### Scenario 2.3: Configure bericht type codes
-- GIVEN the admin navigates to zaaktype configuration in `CaseTypeDetail.vue`
-- WHEN they open the "Mijn Overheid" configuration tab
-- THEN they MUST be able to add bericht type codes with: code, label (Dutch description), and default flag
-- AND codes MUST be validated against the Berichtenbox API's accepted codes if the connection is active
-
-#### Scenario 2.4: Bericht type required for send
-- GIVEN the admin has configured bericht type codes for a zaaktype
-- WHEN a case worker attempts to send without selecting a bericht type
-- THEN the system MUST display a validation error: "Selecteer een berichttype"
-- AND the send button MUST be disabled
-
-### Requirement 3: Read tracking for sent messages
-The system MUST track whether the citizen has read the message and surface this in the case timeline.
-
-#### Scenario 3.1: Poll for read status
-- GIVEN a message sent to Berichtenbox with reference ID `msg-abc123`
-- WHEN the Nextcloud background job polls the Berichtenbox API for read status
-- THEN if the message has been read, the case document MUST be updated with the read timestamp
-- AND the case `ActivityTimeline` MUST show: "Bericht gelezen door burger op [datum/tijd]"
-- AND the document's metadata MUST include `readAt` timestamp
-
-#### Scenario 3.2: Unread message after configurable threshold
-- GIVEN a sent message that remains unread for 7 days (default threshold)
-- WHEN the polling job detects the threshold is exceeded
-- THEN the system MUST create a notification for the case worker: "Bericht '[subject]' is na 7 dagen niet gelezen"
-- AND the case timeline MUST show: "Bericht niet gelezen na 7 dagen"
-- AND the case worker SHOULD consider alternative contact methods (phone, post)
-
-#### Scenario 3.3: Polling frequency configuration
-- GIVEN the admin configures Mijn Overheid settings
-- THEN they MUST be able to set the polling interval (default: every 6 hours)
-- AND the maximum polling duration (default: 30 days after send)
-- AND after the maximum duration, polling MUST stop and the message status MUST be set to "onbekend"
-
-#### Scenario 3.4: Read status visible in case overview
-- GIVEN case `zaak-1` has 3 Berichtenbox messages sent
-- WHEN viewing the case's Mijn Overheid section
-- THEN each message MUST show its read status: "Gelezen", "Niet gelezen", or "Onbekend"
-- AND the most recent message's read status MUST be summarized in the case list view
-
-#### Scenario 3.5: Delivery failure handling
-- GIVEN the Berichtenbox API returns a delivery failure for a message (e.g., BSN not registered at Mijn Overheid)
-- THEN the system MUST mark the message as "Niet bezorgd"
-- AND the case timeline MUST show: "Bericht kon niet worden bezorgd: [error reason]"
-- AND the case worker MUST be notified to use an alternative communication channel
-
-### Requirement 4: Message format compliance
-Messages MUST comply with Mijn Overheid Berichtenbox format requirements to ensure delivery.
-
-#### Scenario 4.1: Plain text enforcement
-- GIVEN a case worker composing a Berichtenbox message
-- WHEN they enter the message body
-- THEN the editor MUST be plain text only (no HTML, no rich text, no markdown)
-- AND pasting formatted text MUST strip all formatting
-- AND a character counter MUST show remaining characters (limit: 10,000 characters per Mijn Overheid spec)
-
-#### Scenario 4.2: Required fields validation
-- GIVEN a message being composed
-- WHEN the case worker clicks "Verzenden"
-- THEN the system MUST validate:
- - Subject is present and does not exceed 100 characters
- - Body is present and does not exceed 10,000 characters
- - BSN is present and valid (9-digit, passes 11-check)
- - Bericht type is selected
-- AND missing or invalid fields MUST be highlighted with specific validation error messages
-
-#### Scenario 4.3: Subject line formatting
-- GIVEN a message with subject "Besluit: uw aanvraag omgevingsvergunning OV-2026-001234"
-- THEN the subject MUST NOT contain special characters that Mijn Overheid rejects (control characters, HTML tags)
-- AND the system MUST sanitize the subject before sending
-
-#### Scenario 4.4: Message templates per zaaktype
-- GIVEN zaaktype `omgevingsvergunning` has configured message templates
-- WHEN the case worker opens the message composer
-- THEN they MUST be able to select from templates: "Ontvangstbevestiging", "Besluit vergunning", "Besluit afwijzing"
-- AND selecting a template MUST pre-fill the subject and body with merge fields: `{{zaak.identifier}}`, `{{zaak.title}}`, `{{persoon.naam}}`
-- AND the case worker MUST be able to edit the pre-filled content before sending
-
-#### Scenario 4.5: Message preview before send
-- GIVEN a composed message with template merge fields resolved
-- WHEN the case worker clicks "Voorbeeld"
-- THEN a preview MUST show exactly how the message will appear to the citizen
-- AND the preview MUST highlight any potential issues (empty merge fields, near character limit)
-
-### Requirement 5: Case status push to Mijn Overheid
-The system MUST push case status updates to the Mijn Overheid status page so citizens can track their cases.
-
-#### Scenario 5.1: Push status change to Mijn Overheid
-- GIVEN a case `zaak-1` with linked BSN changes status from "Intake" to "In behandeling"
-- AND Mijn Overheid status push is enabled for this zaaktype
-- WHEN the status change is saved
-- THEN the system MUST call the Mijn Overheid Zaakstatus API with:
- - BSN
- - Zaak identifier
- - New status name and description
- - Status change timestamp
-- AND the API call result MUST be logged in the case timeline
-
-#### Scenario 5.2: Configure status mapping for Mijn Overheid
-- GIVEN a zaaktype has 8 internal statuses
-- WHEN the admin configures Mijn Overheid status mapping
-- THEN they MUST be able to map each internal status to a Mijn Overheid-compatible status label
-- AND some internal statuses MAY be configured as "niet publiceren" (e.g., internal review stages)
-- AND only mapped statuses MUST trigger a push to Mijn Overheid
-
-#### Scenario 5.3: Status push failure retry
-- GIVEN a status push to Mijn Overheid fails due to a network error
-- THEN the system MUST retry the push up to 3 times with exponential backoff (1min, 5min, 15min)
-- AND if all retries fail, the failure MUST be recorded in the case timeline
-- AND the case worker MUST be notified: "Statusupdate naar Mijn Overheid mislukt voor zaak [identifier]"
-
-#### Scenario 5.4: Initial case registration at Mijn Overheid
-- GIVEN a new case is created with a linked BSN
-- AND the zaaktype is configured for Mijn Overheid status updates
-- WHEN the case is saved
-- THEN the system MUST register the case at Mijn Overheid with: zaak identifier, description, expected end date, and initial status
-- AND the Mijn Overheid reference ID MUST be stored on the case
-
-#### Scenario 5.5: Case completion notification via status page
-- GIVEN a case reaches its final status (isFinal: true)
-- WHEN the status is pushed to Mijn Overheid
-- THEN the status MUST include: "Afgehandeld" with the result type and a link to collect the decision document
-- AND if the decision was sent via Berichtenbox, the status MUST reference the message
-
-### Requirement 6: DigiD-authenticated citizen portal
-Citizens MUST be able to view their case status via a DigiD-authenticated portal page.
-
-#### Scenario 6.1: Citizen views case status
-- GIVEN a citizen authenticates via DigiD on the municipality's website
-- WHEN they navigate to "Mijn zaken" (my cases)
-- THEN the system MUST query Procest for all cases linked to the citizen's BSN
-- AND display each case with: zaak identifier, type, current status, start date, and expected end date
-
-#### Scenario 6.2: Case detail in citizen portal
-- GIVEN a citizen clicks on case `zaak-1` in "Mijn zaken"
-- THEN they MUST see:
- - Current status with a visual status timeline
- - Key dates (submitted, expected completion)
- - Documents available for download (only "Openbaar" documents and sent messages)
- - Assigned case worker name (if configured to show)
- - Contact information for questions
-
-#### Scenario 6.3: Portal as API for municipal website
-- GIVEN the citizen portal is implemented as an API rather than a standalone UI
-- THEN Procest MUST expose a public API endpoint (`/api/public/mijn-zaken`) that accepts a DigiD-authenticated BSN
-- AND returns case data in a standardized JSON format
-- AND the municipality's website (or Mijn Overheid status page) renders the data
-
-#### Scenario 6.4: Portal respects privacy settings
-- GIVEN a case has documents with vertrouwelijkheidaanduiding "VERTROUWELIJK" or higher
-- WHEN the citizen views the case in the portal
-- THEN those documents MUST NOT be visible or downloadable
-- AND only documents explicitly marked for citizen access MUST be shown
-
-### Requirement 7: Admin configuration for Mijn Overheid connection
-Administrators MUST be able to configure the Mijn Overheid API connection with certificate-based authentication.
-
-#### Scenario 7.1: Configure API credentials
-- GIVEN the Procest admin settings page
-- WHEN the admin navigates to the "Mijn Overheid" configuration section
-- THEN they MUST be able to enter:
- - API endpoint URL (SOAP or REST, depending on integration variant)
- - OIN (Organisatie-identificatienummer) of the municipality
- - PKIoverheid certificate (upload or file path)
- - Private key (securely stored in Nextcloud's credential store)
-- AND a "Test verbinding" button MUST verify connectivity by calling the Berichtenbox ping endpoint
-- AND the connection status MUST be displayed: "Verbonden" (green) or "Niet verbonden" (red with error)
-
-#### Scenario 7.2: Configure bericht type codes per zaaktype
-- GIVEN the zaaktype configuration screen in `CaseTypeDetail.vue`
-- WHEN the admin opens the "Mijn Overheid" tab
-- THEN they MUST be able to:
- - Add bericht type codes with code and label
- - Set a default bericht type for the zaaktype
- - Configure message templates with merge fields
- - Enable/disable status push to Mijn Overheid
- - Map internal statuses to Mijn Overheid status labels
-
-#### Scenario 7.3: Certificate expiration monitoring
-- GIVEN a PKIoverheid certificate is configured
-- THEN the system MUST check the certificate's expiration date daily
-- AND when the certificate expires within 30 days, notify the admin: "PKIoverheid certificaat verloopt op [datum]. Vernieuw het certificaat."
-- AND when the certificate has expired, disable Mijn Overheid integration with error: "Certificaat verlopen. Mijn Overheid berichten kunnen niet worden verzonden."
-
-#### Scenario 7.4: Test mode (stuuring omgeving)
-- GIVEN the admin wants to test the integration without sending to real citizens
-- WHEN they enable "Test modus" in the Mijn Overheid configuration
-- THEN all API calls MUST be routed to the Mijn Overheid staging environment (stuuromgeving)
-- AND sent messages MUST be clearly marked as test messages in the case timeline
-- AND a banner MUST appear in the case worker UI: "Mijn Overheid: testmodus actief"
-
-#### Scenario 7.5: Connection via OpenConnector
-- GIVEN the municipality routes all external API calls through OpenConnector
-- WHEN configuring Mijn Overheid
-- THEN the admin MUST be able to select an OpenConnector source instead of direct API configuration
-- AND the OpenConnector source MUST handle the mTLS certificate, URL routing, and authentication
-- AND Procest MUST only send message payloads to OpenConnector
-
-### Requirement 8: Notification channel selection and fallback
-The system MUST support selecting the appropriate notification channel per case and fall back to alternatives when Mijn Overheid is unavailable.
-
-#### Scenario 8.1: Channel selection per citizen
-- GIVEN a case with a linked citizen who has opted out of Mijn Overheid (no DigiD account)
-- WHEN the case worker attempts to send via Berichtenbox
-- THEN the system MUST display: "Deze burger is niet bereikbaar via Mijn Overheid"
-- AND suggest alternative channels: email (if available) or postal mail
-- AND the case worker MUST be able to send via the alternative channel
-
-#### Scenario 8.2: Automatic channel detection
-- GIVEN a case with a linked BSN
-- WHEN the case worker opens the message composer
-- THEN the system MUST check whether the BSN is registered at Mijn Overheid (via the Berichtenbox API)
-- AND if registered, default to Berichtenbox
-- AND if not registered, display a warning and suggest email
-
-#### Scenario 8.3: Multi-channel sending
-- GIVEN a municipality wants to send both via Berichtenbox and email for critical decisions
-- WHEN the admin configures "dual-channel" for a zaaktype's decision notifications
-- THEN the system MUST send the message via both Berichtenbox and email
-- AND both sends MUST be recorded in the case timeline
-
-#### Scenario 8.4: Postal mail fallback generation
-- GIVEN a citizen is not reachable via Mijn Overheid or email
-- WHEN the case worker selects "Per post verzenden"
-- THEN the system MUST generate a print-ready PDF with the message content and citizen address
-- AND store the PDF as a case document
-- AND record "Bericht per post verzonden" in the timeline with the print date
-
-### Requirement 9: Message audit trail and compliance
-All Berichtenbox message interactions MUST be recorded for compliance with Archiefwet and AVG.
-
-#### Scenario 9.1: Complete audit record per message
-- GIVEN a message is sent via Berichtenbox
-- THEN the audit trail MUST record:
- - Message reference ID (from Berichtenbox API response)
- - BSN of the recipient
- - Subject and body (stored as case document)
- - Bericht type code
- - Sent timestamp
- - Sending user
- - Delivery status (bezorgd/niet bezorgd)
- - Read status and timestamp (when available)
-
-#### Scenario 9.2: Audit entries are immutable
-- GIVEN a Berichtenbox message audit entry
-- THEN it MUST NOT be editable or deletable by any user (including admin)
-- AND it MUST be retained for at least the case's archival retention period per Archiefwet
-
-#### Scenario 9.3: Message export for archival
-- GIVEN a case is being archived (selectielijst retention period reached)
-- WHEN the case is exported for archival
-- THEN all Berichtenbox messages MUST be included as PDF documents with full metadata
-- AND the export MUST include send/read timestamps and delivery status
-
-#### Scenario 9.4: BSN handling compliance
-- GIVEN a BSN is used for Berichtenbox message sending
-- THEN the BSN MUST be transmitted securely (TLS/mTLS only)
-- AND the BSN MUST NOT appear in application logs at INFO level (only DEBUG, and only when explicitly configured)
-- AND the BSN display in the UI MUST be partially masked (e.g., "***99*653") except when explicitly viewing the full BSN
-
-#### Scenario 9.5: Monthly usage reporting
-- GIVEN the admin requests a Mijn Overheid usage report for March 2026
-- THEN the system MUST provide:
- - Total messages sent
- - Messages per zaaktype
- - Delivery success rate
- - Average read time (days between send and read)
- - Messages still unread after threshold
- - API errors and retries
-
-### Requirement 10: Deceased person and special case handling
-The system MUST handle edge cases for citizens who cannot receive Berichtenbox messages.
-
-#### Scenario 10.1: Deceased person detection
-- GIVEN a case linked to BSN `999999655` (a person marked as deceased in BRP)
-- WHEN the case worker attempts to send a Berichtenbox message
-- THEN the system MUST check the person's status in BRP (via OpenRegister/BRP mock or Haal Centraal API)
-- AND if deceased, display: "Deze persoon is overleden. Bericht kan niet worden verzonden via Mijn Overheid."
-- AND suggest contacting the estate executor or next of kin
-
-#### Scenario 10.2: Minor (minderjarige) handling
-- GIVEN a case linked to a person under 14 years old
-- WHEN the case worker attempts to send a Berichtenbox message
-- THEN the system MUST display: "Personen onder 14 jaar hebben geen Mijn Overheid account. Bericht wordt verzonden naar wettelijk vertegenwoordiger."
-- AND the system MUST look up the legal representative's BSN for message routing
-
-#### Scenario 10.3: Organization (niet-natuurlijk persoon) via eHerkenning
-- GIVEN a case linked to an organization with KVK number instead of BSN
-- WHEN the case worker opens the Berichtenbox message composer
-- THEN the system MUST indicate: "Mijn Overheid Berichtenbox is alleen beschikbaar voor burgers (BSN). Gebruik email voor organisaties."
-- AND offer the email channel as the default
-
-## Dependencies
-- Mijn Overheid Berichtenbox API (SOAP/REST, mTLS certificate authentication via Logius)
-- Mijn Overheid Zaakstatus API (for case status push)
-- BSN field on case (via linked person record in OpenRegister role)
-- BRP data (via OpenRegister mock register or Haal Centraal BRP API through OpenConnector)
-- Docudesk (for PDF generation of sent messages and decision documents)
-- Nextcloud background jobs (for read status polling and retry logic)
-- OpenConnector (optional, as API proxy for mTLS handling)
-- DigiD (for citizen portal authentication, not directly integrated in Procest but in the municipality's portal)
-- PKIoverheid (certificate infrastructure for mTLS authentication)
-
----
-
-### Current Implementation Status
-
-**Not yet implemented.** No Mijn Overheid Berichtenbox integration code exists in the Procest codebase. There are no schemas, controllers, services, or Vue components for sending messages to the Berichtenbox or pushing case status to Mijn Overheid.
-
-**Foundation available:**
-- Case detail view (`src/views/cases/CaseDetail.vue`) provides the integration point for a "Bericht verzenden" action in the header actions.
-- Activity timeline (`src/views/cases/components/ActivityTimeline.vue`) could display message sent/read events.
-- Document management (filesPlugin in object store) could store sent messages as case documents.
-- The `dispatch_schema` exists in `SettingsService::SLUG_TO_CONFIG_KEY`, which could be used for message dispatch tracking.
-- `NotificatieService.php` provides notification infrastructure for case worker alerts.
-- Docudesk (external dependency) provides PDF generation for message archival.
-- OpenConnector could host the Berichtenbox API adapter with mTLS handling.
-- BRC controller (`lib/Controller/BrcController.php`) handles decisions, which are the primary content for Berichtenbox messages.
-
-**Partial implementations:** None.
-
-**Mock Registers (dependency):** This spec depends on mock BRP registers being available in OpenRegister for development and testing of BSN-based message sending. These registers are available as JSON files that can be loaded on demand from `openregister/lib/Settings/`.
-
-### Using Mock Register Data
-
-This spec depends on the **BRP** mock register for BSN-based citizen identification and message sending.
-
-**Loading the register:**
-```bash
-# Load BRP register (35 persons, register slug: "brp", schema: "ingeschreven-persoon")
-docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json
-```
-
-**Test data for this spec's use cases:**
-- **Send message to citizen**: BSN `999993653` (Suzanne Moulin) -- test message composition with valid BSN linked to case
-- **Reject without BSN**: Create a case without BSN, verify error "BSN is verplicht voor berichten via Mijn Overheid"
-- **Multiple citizens**: BSN `999990627` (Stephan Janssen), BSN `999992570` (Albert Vogel) -- test message sending to different persons
-- **Deceased person edge case**: BSN `999999655` (Astrid Abels, deceased 2020-06-06) -- test handling of messages to deceased persons
-
-**Querying mock data:**
-```bash
-# Find person by BSN for case linking
-curl "http://localhost:8080/index.php/apps/openregister/api/objects/{brp_register_id}/{person_schema_id}?_search=999993653" -u admin:admin
-```
-
-### Standards & References
-
-- **Mijn Overheid Berichtenbox API**: Government-mandated citizen correspondence channel operated by Logius. Uses SOAP/REST with mTLS certificate authentication.
-- **Mijn Overheid Zaakstatus API**: API for pushing case status updates to the citizen's Mijn Overheid portal.
-- **OIN (Organisatie-identificatienummer)**: Required for government API authentication with Mijn Overheid.
-- **PKIoverheid**: Certificate infrastructure for mTLS authentication.
-- **Digikoppeling**: Dutch government standard for system-to-system communication (required for Berichtenbox).
-- **DigiD**: National authentication service for citizen identity verification.
-- **AVG/GDPR**: BSN processing requires lawful basis and secure handling.
-- **Wet digitale overheid (Wdo)**: Legislation mandating digital government communication channels.
-- **BRP (Basisregistratie Personen)**: BSN lookup for citizen identification and status checking.
-- **Archiefwet**: Archival requirements for government correspondence including Berichtenbox messages.
-- **Haal Centraal BRP API**: Modern REST API for BRP data access (alternative to StUF-BG).
-- **GEMMA**: Mijn Overheid integration is a standard component in the GEMMA reference architecture for citizen communication.
diff --git a/openspec/changes/milestone-tracking/specs/milestone-tracking/spec.md b/openspec/changes/milestone-tracking/specs/milestone-tracking/spec.md
index 69f1814d..ac804ace 100644
--- a/openspec/changes/milestone-tracking/specs/milestone-tracking/spec.md
+++ b/openspec/changes/milestone-tracking/specs/milestone-tracking/spec.md
@@ -11,248 +11,247 @@ Milestone tracking is an established pattern in case management platforms. CMMN
## Context
The existing `StatusTimeline.vue` component already provides a visual progress indicator showing passed/current/future status dots with dates. Status types are ordered and have timestamps when reached (via `statusRecord` schema). This spec extends the status system with a dedicated milestone layer that can be independent of or mapped to status transitions, providing richer progress tracking for both internal users and external stakeholders (citizens, ketenpartners).
-## Requirements
-
+## ADDED Requirements
### Requirement: Milestone sets MUST be configurable per zaaktype
The system SHALL support configurable milestone sets per zaaktype, where each case type defines its own ordered set of milestones with labels, descriptions, and optional automatic triggers.
#### Scenario: Define milestones for a zaaktype
-- GIVEN zaaktype `omgevingsvergunning` is being configured in Settings > Case Types
-- WHEN an admin defines milestones
-- THEN the following milestone set MUST be storable as an ordered array on the caseType object:
+- **GIVEN** zaaktype `omgevingsvergunning` is being configured in Settings > Case Types
+- **WHEN** an admin defines milestones
+- **THEN** the following milestone set MUST be storable as an ordered array on the caseType object:
1. `aanvraag_ontvangen` -- "Aanvraag ontvangen"
2. `documenten_compleet` -- "Documenten compleet"
3. `inhoudelijke_beoordeling` -- "Inhoudelijke beoordeling gestart"
4. `advies_ontvangen` -- "Adviezen ontvangen"
5. `besluit_genomen` -- "Besluit genomen"
6. `beschikking_verzonden` -- "Beschikking verzonden"
-- AND each milestone MUST have: `identifier` (slug), `label` (Dutch display name), `order` (sequence number), optional `description`, and optional `triggerEvent` (n8n webhook event name)
+- **AND** each milestone MUST have: `identifier` (slug), `label` (Dutch display name), `order` (sequence number), optional `description`, and optional `triggerEvent` (n8n webhook event name)
#### Scenario: Different zaaktypes have different milestones
-- GIVEN zaaktype `melding_openbare_ruimte` has 3 milestones and `omgevingsvergunning` has 6
-- WHEN viewing cases of each type
-- THEN each case MUST show progress against its own zaaktype's milestone set
-- AND the progress indicator MUST adapt its width and step count accordingly
+- **GIVEN** zaaktype `melding_openbare_ruimte` has 3 milestones and `omgevingsvergunning` has 6
+- **WHEN** viewing cases of each type
+- **THEN** each case MUST show progress against its own zaaktype's milestone set
+- **AND** the progress indicator MUST adapt its width and step count accordingly
#### Scenario: Milestones can be mapped to status types
-- GIVEN zaaktype `omgevingsvergunning` has both status types and milestones
-- WHEN an admin configures milestone `documenten_compleet`
-- THEN the admin MUST be able to optionally map it to status type `volledigheid_getoetst`
-- AND when a case reaches that status, the milestone MUST be automatically marked as reached
+- **GIVEN** zaaktype `omgevingsvergunning` has both status types and milestones
+- **WHEN** an admin configures milestone `documenten_compleet`
+- **THEN** the admin MUST be able to optionally map it to status type `volledigheid_getoetst`
+- **AND** when a case reaches that status, the milestone MUST be automatically marked as reached
#### Scenario: Milestones can exist independently of status types
-- GIVEN milestone `advies_ontvangen` has no status type mapping
-- WHEN the admin saves the milestone configuration
-- THEN the milestone MUST be valid without a status mapping
-- AND it MUST be triggerable only via manual marking or n8n workflow event
+- **GIVEN** milestone `advies_ontvangen` has no status type mapping
+- **WHEN** the admin saves the milestone configuration
+- **THEN** the milestone MUST be valid without a status mapping
+- **AND** it MUST be triggerable only via manual marking or n8n workflow event
#### Scenario: Admin reorders milestones
-- GIVEN zaaktype `omgevingsvergunning` has 6 milestones
-- WHEN an admin drags milestone 4 to position 2
-- THEN the order numbers MUST be recalculated for all milestones
-- AND existing cases with milestones already reached MUST NOT be affected (historical data preserved)
+- **GIVEN** zaaktype `omgevingsvergunning` has 6 milestones
+- **WHEN** an admin drags milestone 4 to position 2
+- **THEN** the order numbers MUST be recalculated for all milestones
+- **AND** existing cases with milestones already reached MUST NOT be affected (historical data preserved)
### Requirement: Milestones MUST be reached automatically or manually with audit trail
The system SHALL support reaching milestones automatically or manually with audit trail; milestones can be triggered by n8n workflow events, status transitions, or marked manually by case workers.
#### Scenario: Automatic milestone from n8n workflow event
-- GIVEN milestone `documenten_compleet` has `triggerEvent` set to `all_documents_received`
-- WHEN the n8n workflow sends a webhook to `/api/cases/{zaak-1}/milestones/trigger` with event `all_documents_received`
-- THEN milestone `documenten_compleet` MUST be marked as reached
-- AND the timestamp of the event MUST be recorded
-- AND the trigger source MUST be recorded as "workflow" with the n8n execution ID
+- **GIVEN** milestone `documenten_compleet` has `triggerEvent` set to `all_documents_received`
+- **WHEN** the n8n workflow sends a webhook to `/api/cases/{zaak-1}/milestones/trigger` with event `all_documents_received`
+- **THEN** milestone `documenten_compleet` MUST be marked as reached
+- **AND** the timestamp of the event MUST be recorded
+- **AND** the trigger source MUST be recorded as "workflow" with the n8n execution ID
#### Scenario: Automatic milestone from status transition
-- GIVEN milestone `besluit_genomen` is mapped to status type `besluit`
-- WHEN a case worker changes case `zaak-1` to status `besluit` via the QuickStatusDropdown
-- THEN milestone `besluit_genomen` MUST be automatically marked as reached
-- AND the trigger source MUST be recorded as "status_transition" with the status record ID
+- **GIVEN** milestone `besluit_genomen` is mapped to status type `besluit`
+- **WHEN** a case worker changes case `zaak-1` to status `besluit` via the QuickStatusDropdown
+- **THEN** milestone `besluit_genomen` MUST be automatically marked as reached
+- **AND** the trigger source MUST be recorded as "status_transition" with the status record ID
#### Scenario: Manual milestone marking with reason
-- GIVEN milestone `advies_ontvangen` has no automatic trigger configured
-- WHEN a case worker manually marks the milestone as reached on case `zaak-1`
-- THEN the milestone MUST be recorded with: the case worker's user ID, current timestamp, and an optional reason text
-- AND the trigger source MUST be recorded as "manual"
+- **GIVEN** milestone `advies_ontvangen` has no automatic trigger configured
+- **WHEN** a case worker manually marks the milestone as reached on case `zaak-1`
+- **THEN** the milestone MUST be recorded with: the case worker's user ID, current timestamp, and an optional reason text
+- **AND** the trigger source MUST be recorded as "manual"
#### Scenario: Milestone reversal requires justification
-- GIVEN milestone 3 of 6 is reached for case `zaak-1`
-- WHEN a case worker with coordinator role attempts to unmark milestone 3
-- THEN the system MUST require a mandatory reason text for the reversal
-- AND the reversal MUST be recorded in the audit trail with: user, timestamp, original reached date, and reason
-- AND the milestone's `reached` flag MUST be set to false and `reversedAt` timestamp recorded
+- **GIVEN** milestone 3 of 6 is reached for case `zaak-1`
+- **WHEN** a case worker with coordinator role attempts to unmark milestone 3
+- **THEN** the system MUST require a mandatory reason text for the reversal
+- **AND** the reversal MUST be recorded in the audit trail with: user, timestamp, original reached date, and reason
+- **AND** the milestone's `reached` flag MUST be set to false and `reversedAt` timestamp recorded
#### Scenario: Non-coordinator cannot reverse milestones
-- GIVEN a case worker with behandelaar role
-- WHEN they attempt to reverse a reached milestone
-- THEN the system MUST deny the action with message "Alleen een coordinator kan mijlpalen terugdraaien"
+- **GIVEN** a case worker with behandelaar role
+- **WHEN** they attempt to reverse a reached milestone
+- **THEN** the system MUST deny the action with message "Alleen een coordinator kan mijlpalen terugdraaien"
### Requirement: Cases MUST display visual milestone progress indicators
The system SHALL display visual milestone progress indicators, showing milestone progress as a step indicator in both list and detail views.
#### Scenario: Progress indicator in case list view
-- GIVEN 3 cases exist: one at milestone 2/6, one at 4/6, one at 6/6
-- WHEN viewing the case list (CaseList.vue)
-- THEN each case row MUST show a compact progress indicator (e.g., "2/6 Documenten compleet")
-- AND completed cases (6/6) MUST show a green checkmark icon
-- AND the progress indicator MUST use NL Design System progress bar tokens
+- **GIVEN** 3 cases exist: one at milestone 2/6, one at 4/6, one at 6/6
+- **WHEN** viewing the case list (CaseList.vue)
+- **THEN** each case row MUST show a compact progress indicator (e.g., "2/6 Documenten compleet")
+- **AND** completed cases (6/6) MUST show a green checkmark icon
+- **AND** the progress indicator MUST use NL Design System progress bar tokens
#### Scenario: Step indicator in case detail view
-- GIVEN case `zaak-1` has milestone 3 of 6 reached
-- WHEN viewing the case detail (CaseDetail.vue)
-- THEN a horizontal step indicator MUST show all 6 milestones below the status card
-- AND milestones 1-3 MUST be marked as reached with green dots and timestamps on hover
-- AND milestones 4-6 MUST be shown as pending with grey dots
-- AND the current milestone (3) MUST be visually highlighted with a larger dot or accent color
+- **GIVEN** case `zaak-1` has milestone 3 of 6 reached
+- **WHEN** viewing the case detail (CaseDetail.vue)
+- **THEN** a horizontal step indicator MUST show all 6 milestones below the status card
+- **AND** milestones 1-3 MUST be marked as reached with green dots and timestamps on hover
+- **AND** milestones 4-6 MUST be shown as pending with grey dots
+- **AND** the current milestone (3) MUST be visually highlighted with a larger dot or accent color
#### Scenario: Step indicator is accessible
-- GIVEN the milestone step indicator is rendered
-- THEN it MUST have `role="progressbar"` with `aria-valuenow`, `aria-valuemin`, and `aria-valuemax`
-- AND each milestone dot MUST be keyboard-focusable with `aria-label` describing the milestone name and status
-- AND color MUST NOT be the only indicator of milestone state (use icons + text)
+- **GIVEN** the milestone step indicator is rendered
+- **THEN** it MUST have `role="progressbar"` with `aria-valuenow`, `aria-valuemin`, and `aria-valuemax`
+- **AND** each milestone dot MUST be keyboard-focusable with `aria-label` describing the milestone name and status
+- **AND** color MUST NOT be the only indicator of milestone state (use icons + text)
#### Scenario: Milestone detail panel shows full history
-- GIVEN a case worker clicks on a reached milestone dot
-- THEN a tooltip or panel MUST show: milestone label, description, reached date/time, trigger source (manual/workflow/status), and who triggered it
-- AND for reversed milestones, the reversal history MUST also be shown
+- **GIVEN** a case worker clicks on a reached milestone dot
+- **THEN** a tooltip or panel MUST show: milestone label, description, reached date/time, trigger source (manual/workflow/status), and who triggered it
+- **AND** for reversed milestones, the reversal history MUST also be shown
#### Scenario: StatusTimeline and milestone indicator coexist
-- GIVEN a case has both status types and milestones configured
-- WHEN viewing the case detail
-- THEN the StatusTimeline component MUST remain visible (showing status progression)
-- AND the milestone indicator MUST appear as a separate section labeled "Voortgang"
-- AND both MUST be independently scrollable if they have many items
+- **GIVEN** a case has both status types and milestones configured
+- **WHEN** viewing the case detail
+- **THEN** the StatusTimeline component MUST remain visible (showing status progression)
+- **AND** the milestone indicator MUST appear as a separate section labeled "Voortgang"
+- **AND** both MUST be independently scrollable if they have many items
### Requirement: Milestone timestamps MUST enable duration analysis
The system SHALL track milestone timestamps to enable duration analysis, as time between milestones is tracked for performance reporting and bottleneck detection.
#### Scenario: Calculate time per phase
-- GIVEN case `zaak-1` reached milestone 1 on March 1, milestone 2 on March 5, and milestone 3 on March 15
-- WHEN a manager views the case detail's milestone section
-- THEN the system MUST show duration between consecutive milestones:
+- **GIVEN** case `zaak-1` reached milestone 1 on March 1, milestone 2 on March 5, and milestone 3 on March 15
+- **WHEN** a manager views the case detail's milestone section
+- **THEN** the system MUST show duration between consecutive milestones:
- Phase 1 to 2 (document collection): 4 days
- Phase 2 to 3 (assessment start): 10 days
- Total elapsed: 14 days
#### Scenario: Average milestone duration per zaaktype on dashboard
-- GIVEN 50 completed `omgevingsvergunning` cases exist
-- WHEN a manager views the milestone analytics on the Dashboard (Dashboard.vue)
-- THEN the system MUST show a table with average time between each milestone pair across all completed cases
-- AND milestones where the average exceeds the configured expected duration MUST be highlighted in red
-- AND a trend indicator (arrow up/down) MUST show whether performance is improving or degrading compared to the previous period
+- **GIVEN** 50 completed `omgevingsvergunning` cases exist
+- **WHEN** a manager views the milestone analytics on the Dashboard (Dashboard.vue)
+- **THEN** the system MUST show a table with average time between each milestone pair across all completed cases
+- **AND** milestones where the average exceeds the configured expected duration MUST be highlighted in red
+- **AND** a trend indicator (arrow up/down) MUST show whether performance is improving or degrading compared to the previous period
#### Scenario: Bottleneck detection alert
-- GIVEN the average time between milestones 2 and 3 for `omgevingsvergunning` is 8 days
-- AND 5 active cases have been stuck between milestones 2 and 3 for more than 15 days
-- WHEN the daily analytics job runs
-- THEN the system MUST flag these cases as potential bottlenecks
-- AND notify the coordinator with a summary: "5 zaken wachten langer dan gemiddeld op mijlpaal 'Inhoudelijke beoordeling'"
+- **GIVEN** the average time between milestones 2 and 3 for `omgevingsvergunning` is 8 days
+- **AND** 5 active cases have been stuck between milestones 2 and 3 for more than 15 days
+- **WHEN** the daily analytics job runs
+- **THEN** the system MUST flag these cases as potential bottlenecks
+- **AND** notify the coordinator with a summary: "5 zaken wachten langer dan gemiddeld op mijlpaal 'Inhoudelijke beoordeling'"
### Requirement: Milestone deadlines MUST be trackable with warnings
The system SHALL support trackable milestone deadlines with warnings, as milestones can have expected completion dates based on the case's start date and zaaktype configuration.
#### Scenario: Milestone deadline calculation
-- GIVEN zaaktype `omgevingsvergunning` configures milestone 2 (`documenten_compleet`) with expected duration "5 working days from case start"
-- AND case `zaak-1` starts on 2026-03-01
-- THEN milestone 2's expected deadline MUST be calculated as 2026-03-08 (5 working days)
-- AND the milestone indicator MUST show the expected date for unreached milestones
+- **GIVEN** zaaktype `omgevingsvergunning` configures milestone 2 (`documenten_compleet`) with expected duration "5 working days from case start"
+- **AND** case `zaak-1` starts on 2026-03-01
+- **THEN** milestone 2's expected deadline MUST be calculated as 2026-03-08 (5 working days)
+- **AND** the milestone indicator MUST show the expected date for unreached milestones
#### Scenario: Milestone deadline warning
-- GIVEN milestone 2 of case `zaak-1` has expected deadline 2026-03-08
-- AND the current date is 2026-03-07 (1 day before deadline)
-- AND milestone 2 is not yet reached
-- THEN the milestone dot MUST change to amber color
-- AND a notification MUST be sent to the assigned case worker
+- **GIVEN** milestone 2 of case `zaak-1` has expected deadline 2026-03-08
+- **AND** the current date is 2026-03-07 (1 day before deadline)
+- **AND** milestone 2 is not yet reached
+- **THEN** the milestone dot MUST change to amber color
+- **AND** a notification MUST be sent to the assigned case worker
#### Scenario: Overdue milestone escalation
-- GIVEN milestone 2 of case `zaak-1` has expected deadline 2026-03-08
-- AND the current date is 2026-03-10 (2 days overdue)
-- AND milestone 2 is still not reached
-- THEN the milestone dot MUST change to red color
-- AND a notification MUST be sent to both the case worker and the coordinator
-- AND the case MUST appear in the "Verlopen mijlpalen" section of the dashboard
+- **GIVEN** milestone 2 of case `zaak-1` has expected deadline 2026-03-08
+- **AND** the current date is 2026-03-10 (2 days overdue)
+- **AND** milestone 2 is still not reached
+- **THEN** the milestone dot MUST change to red color
+- **AND** a notification MUST be sent to both the case worker and the coordinator
+- **AND** the case MUST appear in the "Verlopen mijlpalen" section of the dashboard
### Requirement: Milestone dependencies MUST be enforceable
The system SHALL support enforceable milestone dependencies, where milestones can define prerequisites that MUST be met before they can be reached.
#### Scenario: Sequential milestone dependency
-- GIVEN milestone 3 (`inhoudelijke_beoordeling`) requires milestone 2 (`documenten_compleet`) to be reached first
-- WHEN a case worker or workflow attempts to mark milestone 3 as reached while milestone 2 is pending
-- THEN the system MUST reject the action with message "Mijlpaal 'Documenten compleet' moet eerst bereikt zijn"
+- **GIVEN** milestone 3 (`inhoudelijke_beoordeling`) requires milestone 2 (`documenten_compleet`) to be reached first
+- **WHEN** a case worker or workflow attempts to mark milestone 3 as reached while milestone 2 is pending
+- **THEN** the system MUST reject the action with message "Mijlpaal 'Documenten compleet' moet eerst bereikt zijn"
#### Scenario: Parallel milestone dependencies
-- GIVEN milestone 5 (`besluit_genomen`) requires both milestone 3 (`inhoudelijke_beoordeling`) and milestone 4 (`advies_ontvangen`)
-- WHEN milestone 3 is reached but milestone 4 is not
-- THEN milestone 5 MUST NOT be reachable
-- AND the milestone indicator MUST show milestone 5 as "wacht op: Adviezen ontvangen"
+- **GIVEN** milestone 5 (`besluit_genomen`) requires both milestone 3 (`inhoudelijke_beoordeling`) and milestone 4 (`advies_ontvangen`)
+- **WHEN** milestone 3 is reached but milestone 4 is not
+- **THEN** milestone 5 MUST NOT be reachable
+- **AND** the milestone indicator MUST show milestone 5 as "wacht op: Adviezen ontvangen"
#### Scenario: No dependency configured allows free-form reaching
-- GIVEN milestone 4 (`advies_ontvangen`) has no dependencies configured
-- WHEN a case worker marks milestone 4 as reached while milestone 2 is still pending
-- THEN the system MUST allow the action
-- AND the milestone indicator MUST show milestones 1 and 4 as reached, with 2 and 3 still pending
+- **GIVEN** milestone 4 (`advies_ontvangen`) has no dependencies configured
+- **WHEN** a case worker marks milestone 4 as reached while milestone 2 is still pending
+- **THEN** the system MUST allow the action
+- **AND** the milestone indicator MUST show milestones 1 and 4 as reached, with 2 and 3 still pending
### Requirement: Milestone progress MUST be available in the API
The system SHALL make milestone progress available in the API, as external systems (citizen portal, dashboards, ketenpartners) need milestone data via the API.
#### Scenario: API returns full milestone progress for authenticated users
-- GIVEN case `zaak-1` has milestones configured
-- WHEN `GET /api/cases/{zaak-1}/milestones` is called by an authenticated case worker
-- THEN the response MUST include:
+- **GIVEN** case `zaak-1` has milestones configured
+- **WHEN** `GET /api/cases/{zaak-1}/milestones` is called by an authenticated case worker
+- **THEN** the response MUST include:
- Array of milestones with `identifier`, `label`, `order`, `reached` (boolean), `reachedAt` (ISO timestamp or null), `triggerSource`, `triggeredBy`
- `progress`: `{"reached": 3, "total": 6, "percentage": 50}`
- `durations`: array of phase durations between consecutive reached milestones
#### Scenario: Citizen-friendly progress for portal strips internal details
-- GIVEN the citizen portal queries milestone data for a citizen's case via a public share token
-- WHEN `GET /api/public/cases/{token}/milestones` is called
-- THEN only the milestone labels, order, and reached status MUST be returned
-- AND internal identifiers, case worker details, trigger sources, and duration analytics MUST be excluded
-- AND the response MUST include a human-readable `currentStep` field (e.g., "Stap 3 van 6: Inhoudelijke beoordeling")
+- **GIVEN** the citizen portal queries milestone data for a citizen's case via a public share token
+- **WHEN** `GET /api/public/cases/{token}/milestones` is called
+- **THEN** only the milestone labels, order, and reached status MUST be returned
+- **AND** internal identifiers, case worker details, trigger sources, and duration analytics MUST be excluded
+- **AND** the response MUST include a human-readable `currentStep` field (e.g., "Stap 3 van 6: Inhoudelijke beoordeling")
#### Scenario: ZGW-compatible milestone representation
-- GIVEN the ZGW Zaken API exposes case status history via `/api/v1/statussen`
-- WHEN milestones are modeled as enriched status data
-- THEN each milestone MUST be representable as a ZGW-compatible status with `statustype`, `datumStatusGezet`, and `statustoelichting`
-- AND the ZrcController MUST include milestone data in the status history response
+- **GIVEN** the ZGW Zaken API exposes case status history via `/api/v1/statussen`
+- **WHEN** milestones are modeled as enriched status data
+- **THEN** each milestone MUST be representable as a ZGW-compatible status with `statustype`, `datumStatusGezet`, and `statustoelichting`
+- **AND** the ZrcController MUST include milestone data in the status history response
### Requirement: Milestone data MUST be stored as OpenRegister objects
Milestone instances (reached milestones on a case) MUST be stored as structured objects linked to the case.
#### Scenario: Milestone record schema
-- GIVEN the OpenRegister schema for milestone records
-- THEN each milestone record MUST contain: `case` (reference to parent case), `milestoneIdentifier` (slug from caseType config), `reached` (boolean), `reachedAt` (datetime), `triggerSource` (enum: manual/workflow/status_transition), `triggeredBy` (user ID or workflow execution ID), `reversedAt` (datetime, nullable), `reversalReason` (string, nullable)
-- AND the schema MUST be registered as `milestone_record_schema` in `SettingsService::SLUG_TO_CONFIG_KEY`
+- **GIVEN** the OpenRegister schema for milestone records
+- **THEN** each milestone record MUST contain: `case` (reference to parent case), `milestoneIdentifier` (slug from caseType config), `reached` (boolean), `reachedAt` (datetime), `triggerSource` (enum: manual/workflow/status_transition), `triggeredBy` (user ID or workflow execution ID), `reversedAt` (datetime, nullable), `reversalReason` (string, nullable)
+- **AND** the schema MUST be registered as `milestone_record_schema` in `SettingsService::SLUG_TO_CONFIG_KEY`
#### Scenario: Milestone records are created on milestone reach
-- GIVEN milestone `documenten_compleet` is reached on case `zaak-1`
-- WHEN the milestone is marked as reached
-- THEN a new milestone record object MUST be created in the case's register via OpenRegister ObjectService
-- AND the object MUST reference the case via the `case` field
+- **GIVEN** milestone `documenten_compleet` is reached on case `zaak-1`
+- **WHEN** the milestone is marked as reached
+- **THEN** a new milestone record object MUST be created in the case's register via OpenRegister ObjectService
+- **AND** the object MUST reference the case via the `case` field
#### Scenario: Milestone records support audit trail
-- GIVEN a milestone record for `documenten_compleet` on case `zaak-1`
-- WHEN the record is queried with audit trail enabled
-- THEN the OpenRegister audit trail plugin MUST show: creation event, any updates (reversals), and the full change history
+- **GIVEN** a milestone record for `documenten_compleet` on case `zaak-1`
+- **WHEN** the record is queried with audit trail enabled
+- **THEN** the OpenRegister audit trail plugin MUST show: creation event, any updates (reversals), and the full change history
### Requirement: Dashboard MUST show milestone-based KPIs
The Procest dashboard MUST include milestone-based performance indicators.
#### Scenario: Milestone completion rate KPI card
-- GIVEN the Dashboard.vue already shows KPI cards
-- WHEN a coordinator views the dashboard
-- THEN a "Mijlpaalvoortgang" KPI card MUST show: number of cases on track (milestone deadlines met), number of cases with overdue milestones, and overall on-time percentage
+- **GIVEN** the Dashboard.vue already shows KPI cards
+- **WHEN** a coordinator views the dashboard
+- **THEN** a "Mijlpaalvoortgang" KPI card MUST show: number of cases on track (milestone deadlines met), number of cases with overdue milestones, and overall on-time percentage
#### Scenario: Milestone funnel visualization
-- GIVEN 100 active `omgevingsvergunning` cases
-- WHEN a manager views the milestone analytics panel
-- THEN a funnel chart MUST show how many cases are at each milestone stage (e.g., 30 at milestone 1, 25 at milestone 2, etc.)
-- AND the funnel MUST visually indicate where cases are clustering (potential bottlenecks)
+- **GIVEN** 100 active `omgevingsvergunning` cases
+- **WHEN** a manager views the milestone analytics panel
+- **THEN** a funnel chart MUST show how many cases are at each milestone stage (e.g., 30 at milestone 1, 25 at milestone 2, etc.)
+- **AND** the funnel MUST visually indicate where cases are clustering (potential bottlenecks)
#### Scenario: Filter dashboard by milestone
-- GIVEN the case list view
-- WHEN a case worker selects filter "Mijlpaal: Documenten compleet (bereikt)"
-- THEN only cases that have reached milestone `documenten_compleet` MUST be shown
-- AND a complementary filter "Mijlpaal: Documenten compleet (niet bereikt)" MUST also be available
+- **GIVEN** the case list view
+- **WHEN** a case worker selects filter "Mijlpaal: Documenten compleet (bereikt)"
+- **THEN** only cases that have reached milestone `documenten_compleet` MUST be shown
+- **AND** a complementary filter "Mijlpaal: Documenten compleet (niet bereikt)" MUST also be available
## Non-Requirements
- This spec does NOT cover BPMN/CMMN model import or visual process modeling
diff --git a/openspec/changes/mobiel-inspectie/specs/mobiel-inspectie/spec.md b/openspec/changes/mobiel-inspectie/specs/mobiel-inspectie/spec.md
deleted file mode 100644
index bbb0963d..00000000
--- a/openspec/changes/mobiel-inspectie/specs/mobiel-inspectie/spec.md
+++ /dev/null
@@ -1,578 +0,0 @@
----
-status: implemented
----
-# Mobiel Inspectie Specification
-
-## Purpose
-
-Mobiel Inspectie provides field inspectors with a Progressive Web App (PWA) for conducting inspections on location. Inspectors need to complete checklists, take photos, capture GPS coordinates, and add observations -- often in areas with poor or no connectivity. The app syncs data when back online.
-
-**Tender demand**: 16% of tenders (11/69) explicitly require mobile inspection. It is a critical differentiator for VTH tenders -- mobile inspection is the primary tool for field inspectors at omgevingsdiensten.
-**Standards**: PWA (Progressive Web App), Service Workers (offline), Geolocation API, MediaStream API (camera)
-**Feature tier**: V2 (online PWA with photo/GPS), V3 (offline capability, sync queue, field signatures)
-
-## Requirements
-
----
-
-### REQ-MOB-01: PWA Installation and Access
-
-The system MUST provide a Progressive Web App that inspectors can install on mobile devices and access from the browser. The PWA MUST integrate with Nextcloud authentication and launch in standalone mode for a native-like experience.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-01a: Install PWA on mobile device
-
-- GIVEN an inspector accessing Procest from a mobile browser (Chrome/Safari)
-- WHEN the inspector navigates to the inspectie module
-- THEN the browser MUST offer "Add to Home Screen" via the PWA manifest
-- AND the installed app MUST launch in standalone mode (no browser chrome)
-- AND the app MUST use the Nextcloud authentication token for secure access
-
-#### Scenario MOB-01b: Responsive layout for mobile
-
-- GIVEN a screen width of 375px (mobile phone)
-- WHEN the inspector views a case or checklist
-- THEN all UI elements MUST be usable without horizontal scrolling
-- AND touch targets MUST be at least 44x44px (WCAG 2.5.5)
-- AND the primary actions (complete item, take photo, add note) MUST be accessible within one tap
-
-#### Scenario MOB-01c: PWA manifest configuration
-
-- GIVEN the Procest app deployed on a Nextcloud instance
-- WHEN the PWA manifest is requested at `/apps/procest/manifest.json`
-- THEN it MUST include: `name` ("Procest Inspectie"), `short_name` ("Inspectie"), `start_url` (inspectie module URL), `display` ("standalone"), `orientation` ("portrait"), `theme_color` (Nextcloud primary), `background_color` (white), icons at 192x192 and 512x512
-- AND the HTML entry point MUST include ``
-
-#### Scenario MOB-01d: Session persistence on PWA launch
-
-- GIVEN an inspector who installed the PWA and previously logged into Nextcloud
-- WHEN the inspector opens the PWA from the home screen 48 hours later
-- THEN the session MUST still be active if the Nextcloud session token has not expired
-- AND if the session has expired, the inspector MUST be redirected to the Nextcloud login page within the standalone PWA window
-
-#### Scenario MOB-01e: Landscape mode for tablets
-
-- GIVEN an inspector using a tablet in landscape orientation (1024x768)
-- WHEN the inspector views a checklist
-- THEN the layout MUST use a split view: checklist items on the left, detail/photo area on the right
-- AND the split ratio MUST be adjustable via drag handle
-
----
-
-### REQ-MOB-02: Inspection Task List
-
-The system MUST display the inspector's assigned inspection tasks for the current day or configurable period, sourced from OpenRegister task objects assigned to the inspector.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-02a: Today's inspections
-
-- GIVEN inspector "Pieter" with 4 inspections scheduled for today:
- - 09:00 Bouwtoezicht fase 1 -- Keizersgracht 100
- - 10:30 Milieucontrole -- Industrieweg 5
- - 13:00 Bouwtoezicht fase 2 -- Prinsengracht 50
- - 15:00 Horeca-inspectie -- Leidseplein 12
-- WHEN Pieter opens the app
-- THEN the task list MUST show all 4 inspections ordered by time
-- AND each item MUST show: address, type, case reference, time
-- AND each item MUST have a "Navigeer" button that opens the address in the device's map app
-
-#### Scenario MOB-02b: Filter by date range
-
-- GIVEN Pieter has 12 inspections scheduled across the current week
-- WHEN Pieter selects the date filter and chooses "Deze week"
-- THEN the task list MUST show all 12 inspections grouped by day
-- AND each day header MUST show the date and number of inspections (e.g., "Maandag 16 maart -- 3 inspecties")
-
-#### Scenario MOB-02c: Empty task list
-
-- GIVEN inspector "Lisa" has no inspections scheduled for today
-- WHEN Lisa opens the app
-- THEN the task list MUST show an empty state message: "Geen inspecties gepland voor vandaag"
-- AND a link to "Bekijk komende inspecties" that shows the next 7 days
-
-#### Scenario MOB-02d: Task list data source from OpenRegister
-
-- GIVEN inspection tasks are stored as OpenRegister objects in the `procest` register with the `task` schema
-- AND task objects have `assignee` matching the current Nextcloud user ID
-- AND task objects have `taskType` = "inspection"
-- WHEN the app fetches the task list
-- THEN it MUST query the OpenRegister API: `GET /api/objects?register={procest}&schema={task}&_filter[assignee]={userId}&_filter[taskType]=inspection&_order[scheduledDate]=asc`
-- AND parse the response into the task list view
-
-#### Scenario MOB-02e: Route optimization suggestion
-
-- GIVEN inspector "Pieter" has 4 inspections at different addresses
-- WHEN Pieter taps "Optimaliseer route"
-- THEN the system MUST open the device's map app with all 4 addresses as waypoints
-- AND the waypoints SHOULD be ordered to minimize travel distance (using browser geolocation as start point)
-
----
-
-### REQ-MOB-03: Checklist Completion
-
-The system MUST support completing inspection checklists on the mobile device. Checklists are defined as OpenRegister objects per case type and consist of categorized items with configurable response options.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-03a: Complete a checklist item
-
-- GIVEN a checklist "Bouwtoezicht fase 1" with 8 items in 3 categories:
- - Fundering (3 items): Fundering conform tekening, Waterdichting aangebracht, Drainage aanwezig
- - Constructie (3 items): Wapening conform bestek, Betonkwaliteit gecontroleerd, Dekking wapening voldoende
- - Veiligheid (2 items): Bouwplaats afgezet, Veiligheidsmaatregelen getroffen
-- WHEN the inspector selects item "Fundering conform tekening"
-- THEN the inspector MUST be able to select: Conform / Niet-conform / Niet van toepassing
-- AND add a free-text toelichting (max 2000 characters)
-- AND the progress indicator MUST update: "3/8 items completed"
-
-#### Scenario MOB-03b: Mandatory photo on non-conformity
-
-- GIVEN a checklist item configured with "foto verplicht bij niet-conform"
-- WHEN the inspector marks the item as "Niet-conform"
-- THEN the system MUST require at least one photo before the item can be saved
-- AND the photo MUST be captured via the device camera (not from gallery, for evidentiary integrity)
-- AND the save button MUST be disabled with tooltip "Voeg minimaal 1 foto toe" until a photo is attached
-
-#### Scenario MOB-03c: Checklist category navigation
-
-- GIVEN a checklist with 25 items across 5 categories
-- WHEN the inspector views the checklist
-- THEN categories MUST be shown as collapsible sections with completion indicators (e.g., "Fundering 2/5")
-- AND tapping a category header MUST expand/collapse that section
-- AND a sticky header MUST show overall progress: "12/25 items (48%)"
-
-#### Scenario MOB-03d: Checklist item with numeric measurement
-
-- GIVEN a checklist item "Geluidsniveau (dB)" configured with response type "numeric" and range 0-120
-- WHEN the inspector enters a value of 85
-- THEN the value MUST be validated against the configured range
-- AND if the value exceeds the threshold (e.g., >80 dB), a warning MUST be displayed: "Waarde overschrijdt norm"
-- AND the measurement MUST be stored with the checklist response
-
-#### Scenario MOB-03e: Resume partially completed checklist
-
-- GIVEN an inspector who completed 5 of 8 checklist items and closed the app
-- WHEN the inspector reopens the app and navigates to the same inspection
-- THEN all 5 completed items MUST be shown with their previous responses
-- AND the checklist MUST scroll to the first incomplete item
-- AND a banner MUST show: "5 van 8 items ingevuld -- ga verder waar u bent gebleven"
-
----
-
-### REQ-MOB-04: Photo and Document Capture
-
-The system MUST support capturing photos and attaching them to inspection cases and specific checklist items. Photos MUST include metadata (GPS, timestamp) and be stored in Nextcloud Files.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-04a: Take inspection photo
-
-- GIVEN an active inspection on case "2026-089"
-- WHEN the inspector taps "Foto maken"
-- THEN the device camera MUST open
-- AND after capturing, the photo MUST be linked to: the case, the specific checklist item (if applicable), GPS coordinates, timestamp
-- AND the photo MUST be uploaded to Nextcloud Files under the case folder at `/Procest/Zaken/2026-089/Inspecties/{inspectieId}/`
-
-#### Scenario MOB-04b: Annotate photo
-
-- GIVEN a captured photo of a building facade
-- WHEN the inspector taps "Markeren"
-- THEN the inspector MUST be able to draw arrows, circles, or rectangles on the photo to highlight issues
-- AND select colors (red, yellow, green) for annotations
-- AND the annotated version MUST be saved alongside the original (both stored in Nextcloud Files)
-- AND the original MUST be preserved unmodified for evidentiary purposes
-
-#### Scenario MOB-04c: Multiple photos per checklist item
-
-- GIVEN a checklist item "Fundering conform tekening" marked as "Niet-conform"
-- WHEN the inspector has captured 3 photos for this item
-- THEN all 3 photos MUST be displayed as thumbnails below the checklist item
-- AND each thumbnail MUST be tappable to view full-size
-- AND a delete button (trash icon) MUST allow removing a photo with confirmation dialog
-- AND the system MUST enforce a maximum of 10 photos per checklist item
-
-#### Scenario MOB-04d: Photo metadata embedding
-
-- GIVEN a photo captured during an inspection at GPS coordinates 52.3676, 4.8913
-- WHEN the photo is saved
-- THEN the following metadata MUST be stored in the OpenRegister photo object:
- - `capturedAt` (ISO 8601 timestamp)
- - `latitude` (52.3676)
- - `longitude` (4.8913)
- - `accuracy` (GPS accuracy in meters)
- - `caseId` (reference to the case)
- - `checklistItemId` (reference to the specific checklist item, if applicable)
- - `inspectorId` (Nextcloud user ID of the inspector)
-- AND the EXIF data in the JPEG file MUST include GPS coordinates and timestamp
-
-#### Scenario MOB-04e: Camera permission handling
-
-- GIVEN an inspector who has not previously granted camera access
-- WHEN the inspector taps "Foto maken" for the first time
-- THEN the browser MUST prompt for camera permission via the MediaStream API
-- AND if permission is denied, the system MUST display: "Camera toegang is vereist voor het maken van inspectie foto's. Ga naar apparaatinstellingen om toegang te verlenen."
-- AND a fallback "Upload foto" button MUST allow selecting from the device gallery (with a warning that gallery photos lack evidentiary integrity)
-
----
-
-### REQ-MOB-05: GPS Location Capture
-
-The system MUST capture GPS coordinates during inspections for geographic verification and audit trail purposes.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-05a: Automatic location recording
-
-- GIVEN an inspector starting an inspection at Keizersgracht 100
-- WHEN the inspection is opened
-- THEN the system MUST request GPS permission and record the current coordinates
-- AND the coordinates MUST be stored on the inspection record (latitude, longitude, accuracy)
-- AND if GPS is unavailable, the system MUST allow manual location confirmation
-
-#### Scenario MOB-05b: Location mismatch warning
-
-- GIVEN an inspection planned for Keizersgracht 100 (52.3676, 4.8913)
-- AND the inspector's GPS shows coordinates >500m from the planned location
-- THEN the system MUST display a warning: "Uw locatie wijkt af van het inspectieadres (afstand: {distance}m)"
-- AND the inspector MUST confirm to proceed with reason selection: "Ander adres", "GPS onnauwkeurig", "Inspectie op afstand"
-- AND the override reason MUST be stored in the inspection audit trail
-
-#### Scenario MOB-05c: Continuous location tracking during inspection
-
-- GIVEN an active inspection in progress
-- WHEN the inspector moves between areas on a large site (e.g., construction site)
-- THEN the system SHOULD record GPS coordinates at each checklist item completion
-- AND a location trail MUST be available in the inspection report
-- AND battery consumption MUST be minimized by using the Geolocation API watchPosition with `{ enableHighAccuracy: true, maximumAge: 30000, timeout: 10000 }`
-
-#### Scenario MOB-05d: Indoor location fallback
-
-- GIVEN an inspector inside a building where GPS signal is weak (accuracy >100m)
-- WHEN the system detects poor GPS accuracy
-- THEN it MUST display: "GPS signaal zwak -- locatie is mogelijk onnauwkeurig"
-- AND the inspector MUST be able to manually pin the location on a map
-- AND the manually pinned location MUST be flagged as `locationSource: "manual"` vs. `locationSource: "gps"`
-
-#### Scenario MOB-05e: Map integration for planned inspections
-
-- GIVEN a list of today's inspections with known addresses
-- WHEN the inspector taps the map icon in the header
-- THEN a map view MUST show all planned inspection locations as pins
-- AND completed inspections MUST be shown with a green checkmark pin
-- AND the inspector's current location MUST be shown as a blue dot
-- AND tapping a pin MUST show the inspection summary with a "Start inspectie" button
-
----
-
-### REQ-MOB-06: Offline Capability
-
-The system MUST support offline operation for areas with no network connectivity. Data MUST be queued locally and synced when connectivity returns.
-
-**Feature tier**: V3
-
-
-#### Scenario MOB-06a: Work offline
-
-- GIVEN the inspector has loaded today's inspections while online
-- WHEN network connectivity is lost
-- THEN the inspector MUST still be able to: view assigned inspections, complete checklist items, take photos, add notes
-- AND a "Offline" indicator MUST be shown in the app header (orange banner)
-- AND all changes MUST be stored in the browser's IndexedDB
-
-#### Scenario MOB-06b: Sync when back online
-
-- GIVEN 2 inspections completed offline with 6 photos and 16 checklist responses
-- WHEN network connectivity is restored
-- THEN the system MUST automatically sync all queued data to the server
-- AND a sync progress indicator MUST show: "Synchroniseren: 6/6 foto's, 16/16 checklistitems"
-- AND any sync conflicts MUST be flagged for manual resolution (server data wins by default)
-- AND the sync status MUST transition through: "Wachten op verbinding" -> "Synchroniseren..." -> "Alles gesynchroniseerd"
-
-#### Scenario MOB-06c: Pre-cache inspection data before going to field
-
-- GIVEN an inspector viewing tomorrow's inspection schedule while online
-- WHEN the inspector taps "Download voor offline gebruik"
-- THEN the Service Worker MUST cache: all inspection task objects, all checklist templates, all case data referenced by inspections, all map tiles for inspection addresses (if supported)
-- AND a progress indicator MUST show: "Offline data downloaden: 3/4 inspecties"
-- AND the cached data MUST be stored in IndexedDB with a `cachedAt` timestamp
-
-#### Scenario MOB-06d: Offline storage capacity management
-
-- GIVEN the browser's IndexedDB storage limit (typically 50-100 MB per origin)
-- WHEN cached offline data exceeds 80% of available storage
-- THEN the system MUST display: "Opslagruimte bijna vol -- synchroniseer om ruimte vrij te maken"
-- AND the system MUST prioritize: pending uploads > current inspection data > historical cache
-- AND completed and synced inspections MUST be evictable from the cache
-
-#### Scenario MOB-06e: Conflict resolution after offline sync
-
-- GIVEN an inspector completed checklist item "Fundering conform tekening" as "Conform" offline
-- AND a colleague updated the same item to "Niet-conform" on the server while the inspector was offline
-- WHEN the offline data syncs
-- THEN the system MUST detect the conflict and present both versions to the inspector
-- AND the conflict dialog MUST show: the offline value, the server value, timestamps, and author for each
-- AND the inspector MUST choose: "Mijn versie behouden", "Server versie accepteren", or "Beide bewaren als opmerking"
-
----
-
-### REQ-MOB-07: Inspection Report Generation
-
-The system MUST support generating a structured inspection report from the completed checklist, including all evidence (photos, measurements, notes).
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-07a: Generate report on completion
-
-- GIVEN an inspection with all checklist items completed
-- WHEN the inspector taps "Inspectie afronden"
-- THEN the system MUST generate a PDF report via Docudesk containing:
- - Header: inspection type, case reference, date/time, inspector name
- - Location: address, GPS coordinates, map thumbnail
- - Per checklist category: category name, per item: result (Conform/Niet-conform/N.v.t.), toelichting, embedded photos with annotations
- - Summary: total conform/niet-conform/n.v.t. counts, overall conclusion
- - Footer: inspector digital signature (if V3), generation timestamp
-- AND the report MUST be stored in Nextcloud Files under the case folder
-- AND the case status MAY automatically advance (if configured in the case type workflow)
-
-#### Scenario MOB-07b: Draft report preview
-
-- GIVEN an inspection with 6 of 8 checklist items completed
-- WHEN the inspector taps "Voorvertoning rapport"
-- THEN the system MUST generate a draft PDF with a "CONCEPT" watermark
-- AND incomplete items MUST be highlighted in yellow
-- AND the draft MUST NOT be stored as an official case document
-
-#### Scenario MOB-07c: Report with non-conformities summary
-
-- GIVEN an inspection where 3 of 8 items are marked "Niet-conform"
-- WHEN the report is generated
-- THEN the report MUST include a dedicated "Afwijkingen" section listing all non-conformities
-- AND each non-conformity MUST include: item name, toelichting, photos, and a recommended follow-up action field
-- AND the report conclusion MUST automatically be set to "Niet goedgekeurd" when any non-conformity exists
-
----
-
-### REQ-MOB-08: Inspection Schema and Data Model
-
-The system MUST define OpenRegister schemas for inspection entities: inspection, checklist template, checklist item template, checklist response, and inspection photo.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-08a: Inspection schema definition
-
-- GIVEN the Procest app repair step runs (`InitializeSettings`)
-- THEN the following schemas MUST be created in the `procest` register:
- - `inspection`: `{ caseId, taskId, inspectorId, scheduledDate, startedAt, completedAt, latitude, longitude, accuracy, locationSource, status, overallResult, reportDocumentId }`
- - `checklistTemplate`: `{ title, caseTypeId, categories, version, isActive }`
- - `checklistItemTemplate`: `{ checklistTemplateId, category, title, description, responseType (choice|numeric|text), choices (array), requiredPhotoOnFail, numericRange, sortOrder }`
- - `checklistResponse`: `{ inspectionId, checklistItemTemplateId, response, numericValue, toelichting, respondedAt, respondedBy }`
- - `inspectionPhoto`: `{ inspectionId, checklistResponseId, fileId, capturedAt, latitude, longitude, accuracy, hasAnnotation, annotatedFileId }`
-
-#### Scenario MOB-08b: Checklist template versioning
-
-- GIVEN a checklist template "Bouwtoezicht fase 1" at version 3
-- AND 5 inspections have been completed using version 2
-- WHEN an admin updates the checklist template
-- THEN a new version 4 MUST be created (version 3 becomes immutable)
-- AND existing inspections MUST retain their reference to the version used at the time of inspection
-- AND new inspections MUST use the latest active version
-
-#### Scenario MOB-08c: Admin creates a checklist template
-
-- GIVEN an admin navigating to Procest Settings > Inspectie > Checklists
-- WHEN the admin creates a new checklist for case type "Omgevingsvergunning"
-- THEN the admin MUST be able to: add categories, add items per category, configure response type per item, set photo requirements, order items via drag-and-drop
-- AND the template MUST be stored as an OpenRegister object with the `checklistTemplate` schema
-
----
-
-### REQ-MOB-09: Digital Signature and Field Sign-off
-
-The system MUST support capturing a digital signature from the inspector (and optionally the site responsible person) to sign off the inspection report.
-
-**Feature tier**: V3
-
-
-#### Scenario MOB-09a: Inspector signature capture
-
-- GIVEN an inspector completing an inspection
-- WHEN the inspector taps "Ondertekenen en afronden"
-- THEN a signature canvas MUST appear (full-width, 200px height)
-- AND the inspector MUST draw their signature using touch
-- AND the signature MUST be saved as a PNG image and embedded in the PDF report
-- AND the signature MUST include the signer's name and timestamp
-
-#### Scenario MOB-09b: Third-party signature (site responsible)
-
-- GIVEN an inspection that requires sign-off from the site responsible person
-- WHEN the inspector taps "Handtekening derden"
-- THEN the system MUST display: signer name input, signer role input, signature canvas
-- AND the third-party signature MUST be stored separately and embedded in the report
-- AND the inspector MUST confirm they witnessed the signature
-
-#### Scenario MOB-09c: Refuse to sign
-
-- GIVEN a site responsible person who refuses to sign the inspection report
-- WHEN the inspector taps "Weigering registreren"
-- THEN the system MUST record: refusal reason (free text), timestamp, inspector confirmation
-- AND the report MUST include a "Handtekening geweigerd" section with the refusal reason
-
----
-
-### REQ-MOB-10: Inspection Notifications and Reminders
-
-The system MUST support push notifications to remind inspectors of upcoming inspections and alert them of schedule changes.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-10a: Morning briefing notification
-
-- GIVEN inspector "Pieter" has 4 inspections scheduled for today
-- WHEN the time is 07:00 on the inspection day
-- THEN the system MUST send a push notification: "4 inspecties vandaag. Eerste: 09:00 Keizersgracht 100"
-- AND tapping the notification MUST open the PWA to today's task list
-
-#### Scenario MOB-10b: Inspection reminder 30 minutes before
-
-- GIVEN an inspection scheduled for 10:30 at Industrieweg 5
-- WHEN the time is 10:00
-- THEN the system MUST send a push notification: "Inspectie over 30 min: Milieucontrole -- Industrieweg 5"
-- AND the notification MUST include action buttons: "Navigeer" (opens map), "Details" (opens inspection)
-
-#### Scenario MOB-10c: Schedule change notification
-
-- GIVEN an inspection scheduled for 15:00 today
-- WHEN the inspection is rescheduled by the coordinator to 09:00 tomorrow
-- THEN the inspector MUST receive a push notification: "Inspectie verplaatst: Horeca-inspectie Leidseplein 12 -- nieuw: morgen 09:00"
-- AND the task list MUST update to reflect the change (removing from today, adding to tomorrow)
-
----
-
-### REQ-MOB-11: Inspection History and Follow-up
-
-The system MUST maintain a complete inspection history per location and case, enabling inspectors to view previous findings and track follow-up actions.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-11a: View previous inspections at same address
-
-- GIVEN the inspector is starting an inspection at Keizersgracht 100
-- AND 2 previous inspections were conducted at this address (one 3 months ago, one 6 months ago)
-- WHEN the inspector taps "Vorige inspecties"
-- THEN the system MUST show a timeline of previous inspections with: date, inspector name, overall result (goedgekeurd/afgekeurd), number of non-conformities
-- AND tapping an entry MUST show the full inspection report
-
-#### Scenario MOB-11b: Follow-up action creation from non-conformity
-
-- GIVEN an inspection item "Fundering conform tekening" marked as "Niet-conform"
-- WHEN the inspector completes the inspection
-- THEN the system MUST offer to create a follow-up task: "Herinspectie plannen voor: Fundering conform tekening"
-- AND if confirmed, a new task of type "herinspectie" MUST be created in OpenRegister linked to the original inspection and case
-- AND the follow-up task MUST include a deadline based on the case type's remediation period
-
-#### Scenario MOB-11c: Compare current vs. previous inspection
-
-- GIVEN a follow-up inspection at the same address
-- WHEN the inspector views a checklist item that was previously "Niet-conform"
-- THEN the system MUST highlight the item with: "Vorige inspectie: Niet-conform (12-01-2026)"
-- AND the previous toelichting and photos MUST be viewable inline for comparison
-
----
-
-### REQ-MOB-12: Accessibility and Usability
-
-The mobile inspection app MUST be accessible and usable in field conditions, including bright sunlight, wet hands, and gloved operation.
-
-**Feature tier**: V2
-
-
-#### Scenario MOB-12a: High contrast mode for outdoor use
-
-- GIVEN an inspector using the app in bright sunlight
-- WHEN the inspector enables high-contrast mode (via toggle in the header)
-- THEN all text MUST meet WCAG AAA contrast ratio (7:1)
-- AND buttons MUST use solid dark backgrounds with white text
-- AND the status bar indicators (Conform/Niet-conform) MUST use distinct shapes in addition to color (checkmark/cross) for colorblind accessibility
-
-#### Scenario MOB-12b: Large touch targets for gloved use
-
-- GIVEN an inspector wearing safety gloves on a construction site
-- WHEN interacting with checklist items
-- THEN all interactive elements MUST have a minimum touch target of 48x48px (above WCAG 2.5.5 minimum of 44x44px)
-- AND the Conform/Niet-conform/N.v.t. buttons MUST be full-width with 56px height
-- AND swipe gestures MUST be supported: swipe right for Conform, swipe left for Niet-conform
-
-#### Scenario MOB-12c: Voice notes as alternative to typing
-
-- GIVEN an inspector who needs to add a toelichting but cannot easily type (wet hands, gloves)
-- WHEN the inspector taps the microphone icon on the toelichting field
-- THEN the system MUST record audio via the MediaStream API
-- AND the audio file MUST be attached to the checklist response as evidence
-- AND optionally, the system MAY transcribe the audio to text using browser speech recognition API
-
----
-
-## Dependencies
-
-- **VTH Module spec** (`../vth-module/spec.md`): Inspection checklists are defined per VTH case type.
-- **Case Management spec** (`../case-management/spec.md`): Inspections are tasks/activities within cases.
-- **Task Management spec** (`../task-management/spec.md`): Inspection tasks appear in the inspector's task list.
-- **Docudesk**: PDF report generation from checklist data.
-- **Nextcloud Files**: Photo storage under case folder structure.
-- **OpenRegister**: All inspection data stored as objects (inspection, checklist, response, photo schemas).
-- **Nextcloud Push Notifications** (`OCP\Notification\IManager`): For inspection reminders and schedule changes.
-
-### Current Implementation Status
-
-**Not yet implemented.** No mobile inspection, PWA, checklist, or field inspection code exists in the Procest codebase. There are no inspection schemas, no PWA manifest, no service worker, and no mobile-specific Vue components.
-
-**Foundation available:**
-- Task management infrastructure (`src/views/tasks/TaskList.vue`, `src/views/tasks/TaskDetail.vue`) provides the task list model that inspection assignments could use.
-- File upload support via `filesPlugin` in the object store for photo attachment.
-- The `CnDetailPage` component used in case/task detail views provides sidebar and responsive layout foundations.
-- Nextcloud Files integration for document storage under case folders.
-- Docudesk (external dependency) for PDF report generation from checklist data.
-- `MetricsController` and `HealthController` demonstrate the pattern for new API endpoints.
-
-**Partial implementations:** None.
-
-### Standards & References
-
-- **PWA (Progressive Web App)**: W3C standard for installable web apps with offline capability.
-- **Service Workers**: Browser API for offline caching and background sync (required for V3 offline capability).
-- **IndexedDB**: Browser storage API for offline data persistence.
-- **Geolocation API**: W3C standard for GPS coordinate capture.
-- **MediaStream API (getUserMedia)**: Browser API for camera and microphone access.
-- **WCAG 2.5.5**: Touch target size minimum 44x44px for mobile accessibility.
-- **WCAG 2.1 Level AA**: Overall accessibility compliance target.
-- **GEMMA VTH-referentiecomponenten**: Mobile inspection is part of the VTH reference architecture.
-- **Omgevingswet**: Environmental and planning law requiring field inspections for permit compliance.
-- **BIO**: Security requirements for mobile device data handling (device encryption, secure data transmission).
-- **Web Push API**: W3C standard for push notifications to PWA.
-- **Canvas API**: Browser API for photo annotation drawing features.
-- **SpeechRecognition API**: Browser API for voice-to-text transcription.
-
-### Specificity Assessment
-
-This spec defines 12 requirements with 3-5 scenarios each, covering the full mobile inspection lifecycle from PWA installation through report generation. The V2/V3 tier split is maintained.
-
-**Competitive context:** Dimpact ZAC and Flowable do not offer native mobile inspection PWAs. ZAC relies on third-party mobile inspection tools. Procest's built-in mobile inspection is a significant differentiator for VTH tenders.
-
-**Open questions:**
-1. Should the mobile inspection be a separate Vue app (PWA entry point) or part of the main Procest app with responsive layout?
-2. How are checklists defined -- as OpenRegister schemas, JSON templates, or n8n workflow definitions?
-3. What is the maximum number of photos per inspection (storage/bandwidth considerations)?
-4. Should the PWA support Web Push notifications (requires VAPID key configuration)?
-5. How does offline sync handle photo uploads -- queue all or sync progressively?
-6. Should the PWA support multiple simultaneous offline inspections?
diff --git a/openspec/changes/multi-tenant-saas/specs/multi-tenant-saas/spec.md b/openspec/changes/multi-tenant-saas/specs/multi-tenant-saas/spec.md
deleted file mode 100644
index 0efcae97..00000000
--- a/openspec/changes/multi-tenant-saas/specs/multi-tenant-saas/spec.md
+++ /dev/null
@@ -1,301 +0,0 @@
----
-status: implemented
----
-# multi-tenant-saas Specification
-
-## Purpose
-Enable logical data isolation for multiple municipalities on a single Procest/Nextcloud deployment. Each tenant has its own users, cases, configuration, and branding while sharing the platform infrastructure. Cross-tenant access is restricted to platform administrators.
-
-## Context
-The SaaS delivery model (shared platform) requires serving multiple municipalities from a single deployment. This reduces operational overhead and enables shared updates. Nextcloud's native architecture is single-instance, but OpenRegister's register model provides a natural isolation boundary: each municipality gets its own register(s), and RBAC enforces access control. The `SettingsService` currently manages a single `procest` register with 26 schemas (case, task, status, role, result, decision, caseType, etc.) -- multi-tenancy requires replicating this register structure per tenant.
-
-## Requirements
-
-### Requirement: Tenant data isolation via OpenRegister registers
-The system MUST ensure complete logical data isolation between tenants by assigning each tenant a dedicated OpenRegister register with its own schema set.
-
-#### Scenario: Tenant-scoped queries return only tenant data
-- GIVEN municipality A and municipality B each have their own OpenRegister register
-- WHEN a case worker from municipality A queries cases via the Procest API
-- THEN only cases stored in municipality A's register MUST be returned
-- AND no data from municipality B MUST be visible in any API response or UI view
-- AND the register ID used for queries MUST be resolved from the user's tenant context
-
-#### Scenario: Tenant-scoped object creation stamps register automatically
-- GIVEN a case worker from municipality A creating a new case
-- WHEN the case is saved via OpenRegister's ObjectService
-- THEN the case MUST be stored in municipality A's register
-- AND the register ID MUST be resolved automatically from the user's tenant membership
-- AND the case worker MUST NOT be able to select or override the target register
-
-#### Scenario: Cross-tenant access returns 404 to prevent information leakage
-- GIVEN a case worker from municipality A who knows a case UUID from municipality B
-- WHEN they attempt to access that case via direct API call (e.g., `GET /api/objects/{register}/{schema}/{uuid}`)
-- THEN the system MUST return HTTP 404 (not 403, to prevent confirming the object exists)
-- AND the access attempt MUST be logged in the security audit trail
-
-#### Scenario: ZGW API endpoints enforce tenant scoping
-- GIVEN the ZrcController, ZtcController, BrcController, and DrcController serve ZGW-compatible APIs
-- WHEN an external system authenticates and queries cases via ZGW endpoints
-- THEN the ZgwService MUST resolve the tenant's register and schema IDs from the authenticated context
-- AND cross-tenant data MUST never be returned even if the external system provides valid object references
-
-#### Scenario: Database-level query isolation
-- GIVEN OpenRegister stores all tenants' objects in the same PostgreSQL database
-- WHEN any query is executed against the objects table
-- THEN the query MUST include a register ID filter as a mandatory WHERE clause
-- AND no query path (including search, listing, and aggregation) MUST bypass the register filter
-
-### Requirement: Tenant identity resolution via Nextcloud groups
-The system MUST determine a user's tenant based on Nextcloud group membership, using a configurable group naming convention.
-
-#### Scenario: User belongs to exactly one tenant group
-- GIVEN user "j.jansen" is a member of Nextcloud group `tenant_gemeente_utrecht`
-- AND the tenant group prefix is configured as `tenant_`
-- WHEN "j.jansen" accesses Procest
-- THEN the system MUST resolve their tenant as "gemeente_utrecht"
-- AND load the corresponding register ID from the tenant configuration
-
-#### Scenario: User belongs to no tenant group
-- GIVEN user "admin" has no group matching the `tenant_` prefix
-- WHEN "admin" accesses Procest
-- THEN the system MUST deny access to case management features
-- AND display a message: "U bent niet gekoppeld aan een organisatie. Neem contact op met uw beheerder."
-
-#### Scenario: User belongs to multiple tenant groups (platform admin)
-- GIVEN user "p.admin" is a member of groups `tenant_gemeente_utrecht` and `tenant_gemeente_amsterdam` and `platform_admin`
-- WHEN "p.admin" accesses Procest
-- THEN the system MUST present a tenant selector in the navigation header
-- AND all actions MUST operate within the selected tenant's context
-- AND tenant switches MUST be logged in the audit trail
-
-### Requirement: Tenant-independent configuration per zaaktype
-Each tenant MUST have independent configuration for zaaktypes, status types, result types, role types, and decision types.
-
-#### Scenario: Tenant-specific zaaktype definitions
-- GIVEN municipality A has zaaktype "Evenementenvergunning" with 5 status types and 3-week processing deadline
-- AND municipality B has zaaktype "Evenementenvergunning" with 3 status types and 6-week processing deadline
-- WHEN each municipality's case workers view available zaaktypes
-- THEN each MUST see only their own municipality's configuration
-- AND the configurations MUST be stored as separate caseType objects in each tenant's register
-
-#### Scenario: Tenant configuration does not leak between tenants
-- GIVEN municipality A creates a new status type "Wacht op extern advies"
-- WHEN municipality B's admin views their status types
-- THEN municipality B MUST NOT see municipality A's new status type
-- AND the settings API (`/apps/procest/api/settings`) MUST return tenant-scoped schema IDs
-
-#### Scenario: Tenant admin manages configuration independently
-- GIVEN a tenant admin for municipality A accesses Settings > Case Types
-- WHEN they modify the "Omgevingsvergunning" zaaktype
-- THEN only municipality A's configuration MUST be affected
-- AND the change MUST be logged with the tenant admin's identity and tenant context
-
-### Requirement: Per-tenant branding via NL Design System tokens
-Each tenant MUST be able to apply its own visual identity using NL Design System design tokens.
-
-#### Scenario: Tenant-specific logo and color scheme
-- GIVEN municipality A has logo "gemeente-a.svg" and primary color `#003366`
-- AND municipality B has logo "gemeente-b.svg" and primary color `#009933`
-- WHEN each municipality's users access Procest
-- THEN the UI MUST display the correct logo and color scheme per tenant
-- AND NL Design System tokens MUST be loaded per tenant from the tenant's configuration
-
-#### Scenario: Tenant branding applies to public-facing pages
-- GIVEN a citizen accesses a shared case link for a municipality A case
-- WHEN the public case view loads
-- THEN the branding MUST reflect municipality A's design tokens
-- AND the branding MUST NOT show Procest or platform branding unless configured
-
-#### Scenario: Tenant without custom branding uses defaults
-- GIVEN a new tenant has not configured custom branding
-- WHEN users access Procest
-- THEN the default NL Design System theme (Rijksoverheid tokens) MUST be applied
-- AND a warning MUST appear in the tenant admin panel: "Huisstijl niet geconfigureerd"
-
-### Requirement: Tenant user management scoped to organization
-Users MUST be scoped to their tenant with appropriate access controls; tenant admins manage only their own users.
-
-#### Scenario: Tenant admin sees only their own users
-- GIVEN a tenant admin for municipality A
-- WHEN they access user management in Procest settings
-- THEN they MUST only see users who are members of the `tenant_gemeente_a` Nextcloud group
-- AND they MUST be able to assign roles (behandelaar, coordinator, admin) within the tenant
-
-#### Scenario: Platform admin cross-tenant access with audit trail
-- GIVEN a platform administrator with the `platform_admin` group
-- WHEN they access the admin panel
-- THEN they MUST see a tenant overview with user counts, case counts, and storage usage per tenant
-- AND they MUST be able to switch between tenants via a tenant selector
-- AND all cross-tenant actions MUST be logged with the platform admin's identity and the target tenant
-
-#### Scenario: User deactivation scopes to tenant only
-- GIVEN tenant admin deactivates user "m.bakker" in municipality A
-- WHEN "m.bakker" is also a member of another tenant group (unusual but possible)
-- THEN only their membership in municipality A's tenant group MUST be affected
-- AND their access to other tenants MUST remain unchanged
-
-### Requirement: Automated tenant provisioning
-The platform MUST support creating and configuring new tenants through an API and admin interface.
-
-#### Scenario: Provision new tenant with default configuration
-- GIVEN a platform administrator
-- WHEN they create a new tenant with name "Gemeente Eindhoven", OIN "00000001002306608000", and slug "gemeente-eindhoven"
-- THEN the system MUST create a Nextcloud group `tenant_gemeente-eindhoven`
-- AND create a dedicated OpenRegister register with all 26 Procest schemas (mirroring `procest_register.json`)
-- AND store the register ID and schema IDs in a tenant configuration record
-- AND create a tenant admin user account assigned to the new group
-- AND the provisioning MUST complete within 30 seconds
-
-#### Scenario: Tenant provisioning via API
-- GIVEN the provisioning API endpoint `POST /api/admin/tenants`
-- WHEN called with `{"name": "Gemeente Eindhoven", "oin": "00000001002306608000", "slug": "gemeente-eindhoven", "adminEmail": "admin@eindhoven.nl"}`
-- THEN the system MUST provision the tenant as described above
-- AND return the tenant configuration including register ID and admin credentials
-- AND send a welcome email to the admin email address
-
-#### Scenario: Tenant provisioning is idempotent
-- GIVEN tenant "gemeente-eindhoven" already exists
-- WHEN the provisioning API is called again with the same slug
-- THEN the system MUST return HTTP 409 Conflict
-- AND the existing tenant MUST NOT be modified
-
-### Requirement: Tenant resource limits and usage monitoring
-The platform MUST enforce configurable resource limits per tenant and provide usage dashboards.
-
-#### Scenario: User limit enforcement
-- GIVEN a tenant configuration with max users set to 50
-- AND the tenant currently has 50 active users
-- WHEN the tenant admin attempts to add a 51st user
-- THEN the system MUST reject the addition with message "Gebruikerslimiet bereikt (50/50)"
-- AND the platform admin MUST be notified
-
-#### Scenario: Storage quota enforcement
-- GIVEN a tenant configuration with max storage set to 10 GB
-- AND current usage is 9.8 GB
-- WHEN a case worker uploads a 300 MB document
-- THEN the system MUST reject the upload with message "Opslaglimiet bijna bereikt"
-- AND the Nextcloud quota system MUST enforce the limit at the group level
-
-#### Scenario: Usage dashboard shows current vs. limits
-- GIVEN a tenant admin views their settings dashboard
-- THEN the dashboard MUST show: active users (42/50), storage used (7.2 GB / 10 GB), total cases (1,247), total documents (3,891)
-- AND items approaching 80% of limit MUST be highlighted in amber
-- AND items exceeding 90% of limit MUST be highlighted in red
-
-### Requirement: Shared resources and template library
-Certain resources MUST be shareable across tenants for efficiency while maintaining tenant isolation on copies.
-
-#### Scenario: Platform-level zaaktype template activation
-- GIVEN a platform-level zaaktype template "WOO verzoek" maintained by the platform admin
-- WHEN a tenant admin activates the template for their tenant
-- THEN the template MUST be deep-copied into the tenant's register as a new caseType object
-- AND all associated status types, result types, and role types MUST also be copied
-- AND modifications to the tenant's copy MUST NOT affect other tenants or the source template
-
-#### Scenario: Template versioning and updates
-- GIVEN a platform template "WOO verzoek" is updated from v1.2 to v1.3
-- AND tenants A and B both have local copies based on v1.2
-- WHEN the platform admin publishes the update
-- THEN tenant admins MUST see a notification: "Template 'WOO verzoek' heeft een update (v1.3)"
-- AND they MUST be able to review changes and choose to apply or skip the update
-- AND applying the update MUST NOT overwrite tenant-specific customizations without confirmation
-
-#### Scenario: Shared reference data remains read-only
-- GIVEN platform-level reference data (e.g., BAG address lookup, BRP integration endpoints)
-- WHEN a tenant admin views the reference data
-- THEN it MUST be read-only at the tenant level
-- AND changes MUST only be possible by the platform admin
-
-### Requirement: Tenant deactivation and data retention
-The platform MUST support deactivating tenants while preserving data according to retention policies.
-
-#### Scenario: Deactivate tenant
-- GIVEN a platform administrator deactivates tenant "gemeente-eindhoven"
-- WHEN the deactivation is processed
-- THEN all users in the tenant's Nextcloud group MUST be blocked from accessing Procest
-- AND the tenant's data MUST remain in OpenRegister (not deleted) for the configured retention period
-- AND the tenant MUST NOT appear in active tenant listings but MUST appear in the archive
-
-#### Scenario: Reactivate tenant within retention period
-- GIVEN tenant "gemeente-eindhoven" was deactivated 30 days ago
-- AND the retention period is 365 days
-- WHEN the platform admin reactivates the tenant
-- THEN all data MUST be restored to active state
-- AND users MUST regain access with their previous roles
-
-#### Scenario: Data purge after retention period
-- GIVEN tenant "gemeente-eindhoven" was deactivated 366 days ago
-- AND the retention period is 365 days
-- WHEN the scheduled purge job runs
-- THEN the platform admin MUST receive a confirmation prompt before purging
-- AND upon confirmation, all tenant data MUST be permanently deleted from OpenRegister
-- AND a purge certificate MUST be generated for compliance records
-
-### Requirement: Cross-tenant reporting for platform administrators
-Platform administrators MUST be able to generate aggregated reports across all tenants without accessing individual case data.
-
-#### Scenario: Platform-wide KPI dashboard
-- GIVEN the platform serves 12 municipalities
-- WHEN the platform admin views the platform dashboard
-- THEN aggregated metrics MUST be shown: total active cases per tenant, average processing time per tenant, SLA compliance percentage per tenant
-- AND no individual case details MUST be visible
-
-#### Scenario: Tenant comparison report
-- GIVEN the platform admin requests a comparison report
-- WHEN the report is generated
-- THEN it MUST show per-tenant: case volume, average resolution time, overdue percentage, user activity
-- AND the report MUST be exportable as CSV and PDF
-
-#### Scenario: Anomaly detection alerts
-- GIVEN tenant "gemeente-utrecht" normally processes 200 cases/month
-- AND the current month shows 50 cases (75% drop)
-- WHEN the daily anomaly check runs
-- THEN the platform admin MUST receive an alert highlighting the unusual pattern
-
-## Non-Requirements
-- This spec does NOT cover database-per-tenant isolation (PostgreSQL schemas or separate databases)
-- This spec does NOT cover multi-region deployment or data residency requirements
-- This spec does NOT cover billing or subscription management
-- This spec does NOT cover SSO federation between tenant identity providers
-
-## Dependencies
-- OpenRegister registers as tenant isolation boundary
-- OpenRegister RBAC for access control enforcement
-- NL Design System tokens for per-tenant branding
-- Nextcloud group-based user management (`OCP\IGroupManager`)
-- Nextcloud quota system for storage limits
-- SettingsService for per-tenant register/schema ID resolution
-- ConfigurationService for automated register provisioning
-
----
-
-### Current Implementation Status
-
-**Not yet implemented.** Multi-tenancy is not currently built into Procest. The following foundational elements exist that could support future multi-tenant work:
-
-- The app uses a single `procest` register in OpenRegister (defined in `lib/Settings/procest_register.json`). Tenant isolation would require creating per-tenant registers using `ConfigurationService::importFromApp()` with tenant-specific parameters.
-- The `InitializeSettings` repair step (`lib/Repair/InitializeSettings.php`) creates the register via `SettingsService.loadConfiguration()` but does not support tenant-scoped register creation.
-- The `SettingsService` stores register and schema IDs as global app config keys (e.g., `register`, `case_schema`) via `IAppConfig`. Multi-tenancy requires per-tenant config storage (e.g., keyed by tenant slug).
-- The frontend object store (`src/store/modules/object.js`) uses `createObjectStore('object')` from `@conduction/nextcloud-vue` which queries a single register/schema pair. No tenant switching logic exists.
-- The settings store (`src/store/modules/settings.js`) fetches from `/apps/procest/api/settings` -- a single global config, not per-tenant.
-- No tenant provisioning UI or API exists.
-- NL Design System theming is supported at the app level via the `nldesign` submodule, but not per-tenant.
-- Nextcloud groups exist but are not used for tenant scoping in Procest.
-
-**Partial foundations:**
-- OpenRegister's register model inherently supports data isolation (one register per tenant is the natural boundary).
-- ZGW API controllers (`lib/Controller/ZrcController.php`, `ZtcController.php`, etc.) use `ZgwService` which reads register/schema IDs from settings -- these could be made tenant-aware by resolving settings per-tenant.
-
-### Standards & References
-
-- **ZGW APIs** (VNG Realisatie): Multi-tenant ZGW deployments are common in Dutch government; the Catalogi API supports multiple catalogues per instance.
-- **Common Ground**: The information layer principle supports data isolation via separate registers per organization.
-- **ISO 27001**: Data isolation requirements for SaaS platforms handling government data.
-- **BIO (Baseline Informatiebeveiliging Overheid)**: Dutch government security baseline requiring logical data separation between organizations.
-- **AVG/GDPR**: Data processing agreements require clear tenant boundaries for personal data.
-- **NL Design System**: Per-organization theming tokens are a standard pattern in Dutch government web applications.
-- **Nextcloud multi-tenant patterns**: Nextcloud does not natively support multi-tenancy; this is typically achieved via app-level isolation using groups.
-- **CMMN 1.1**: No direct multi-tenancy concept, but case plans are scoped to organizational context.
-- **Dimpact ZAC**: Uses separate Open Zaak instances per municipality -- a database-per-tenant approach. Procest's register-per-tenant is a lighter-weight alternative.
-- **Valtimo/Ritense**: Uses Spring Security multi-tenancy with separate database schemas -- similar logical isolation to OpenRegister's register model.
diff --git a/openspec/changes/my-work/specs/my-work/spec.md b/openspec/changes/my-work/specs/my-work/spec.md
deleted file mode 100644
index c31cdc64..00000000
--- a/openspec/changes/my-work/specs/my-work/spec.md
+++ /dev/null
@@ -1,119 +0,0 @@
----
-status: implemented
----
-# my-work Specification
-
-## Purpose
-Document the polish layer applied to the existing "Mijn werk" view in Procest: case-type name display on case items, ARIA labels and keyboard navigation for WCAG AA conformance, and a responsive layout for narrow viewports. The personal workload aggregation and filtering surface itself was shipped earlier and is captured in the canonical `my-work` capability spec; this change formalizes the non-functional requirements applied on top.
-
-## Context
-`MyWork.vue` renders a per-user list of cases and tasks grouped by urgency (overdue, today, this week, later). Items previously showed only entity type and title, with limited keyboard support. This change resolves case type names at mount, attaches ARIA semantics to tabs and items, exposes keyboard activation, and stacks rows on narrow viewports.
-
-## Requirements
-
-### REQ-MYWORK-CT-01: Case Type Name on Case Items
-Case items in the "Mijn werk" view MUST display the case type name as a subtitle.
-
-#### Scenario MYWORK-CT-01-1: Case type resolved from cached collection
-- GIVEN a user opens "Mijn werk"
-- WHEN the view mounts
-- THEN `objectStore.fetchCollection('caseType')` MUST be invoked once to build a lookup map from case type id to title
-- AND each case item MUST display the resolved case type title (e.g., "Omgevingsvergunning") below its title
-
-#### Scenario MYWORK-CT-01-2: Unknown or missing case type
-- GIVEN a case item references a case type id not present in the lookup
-- THEN the subtitle MUST render an empty placeholder (no broken label, no console error)
-
-#### Scenario MYWORK-CT-01-3: Task items unaffected
-- GIVEN a mix of case and task items
-- THEN only case items MUST show the case-type subtitle
-- AND task items MUST continue to show their existing subtitle (e.g., linked case reference)
-
-### REQ-MYWORK-A11Y-01: ARIA Semantics
-The "Mijn werk" view MUST expose ARIA semantics that allow screen readers to announce structure and state.
-
-#### Scenario MYWORK-A11Y-01-1: Filter tabs use tab pattern
-- GIVEN the filter bar at the top of the view
-- THEN the container MUST have `role="tablist"`
-- AND each filter button MUST have `role="tab"` with `aria-selected` reflecting the active state
-
-#### Scenario MYWORK-A11Y-01-2: Section headers expose heading role
-- GIVEN grouped sections "Overdue", "Today", "This week", "Later"
-- THEN each section header MUST have `role="heading"` with an appropriate `aria-level`
-
-#### Scenario MYWORK-A11Y-01-3: Item aria-label
-- GIVEN a case item with title "Bouwvergunning Kerkstraat" that is overdue
-- THEN the item MUST have an `aria-label` announcing entity type, title, and urgency status (e.g., "Zaak: Bouwvergunning Kerkstraat, te laat")
-
-#### Scenario MYWORK-A11Y-01-4: Live region for overdue
-- GIVEN urgency text that changes (e.g., when the day rolls over)
-- THEN the overdue indicator MUST be in an `aria-live="polite"` region so updates are announced without stealing focus
-
-### REQ-MYWORK-KB-01: Keyboard Navigation
-The "Mijn werk" view MUST be fully operable from the keyboard.
-
-#### Scenario MYWORK-KB-01-1: Tab to items
-- GIVEN the view is rendered
-- THEN every item row MUST have `tabindex="0"`
-- AND pressing Tab MUST move focus through items in their visual order
-
-#### Scenario MYWORK-KB-01-2: Activate item with Enter/Space
-- GIVEN an item row has keyboard focus
-- WHEN the user presses Enter or Space
-- THEN the same navigation MUST occur as a mouse click on the item
-
-#### Scenario MYWORK-KB-01-3: Activate filter tab with Enter/Space
-- GIVEN a filter tab has keyboard focus
-- WHEN the user presses Enter or Space
-- THEN the corresponding filter MUST become active and `aria-selected` MUST update
-
-### REQ-MYWORK-RWD-01: Responsive Layout
-The "Mijn werk" view MUST remain readable on narrow viewports.
-
-#### Scenario MYWORK-RWD-01-1: Stack rows at 768px or below
-- GIVEN a viewport of 768 CSS pixels or narrower
-- THEN item rows MUST stack their content vertically (title on top, priority and deadline below)
-- AND padding MUST reduce to remain comfortable on mobile
-
-#### Scenario MYWORK-RWD-01-2: Filter tabs remain operable
-- GIVEN the mobile layout is active
-- THEN filter tabs MUST remain visible and tappable
-- AND each tap target MUST be at least 44x44 CSS pixels
-
-### REQ-MYWORK-FOCUS-01: Focus Management
-The "Mijn werk" view MUST manage focus predictably when items, filters, or sections change.
-
-#### Scenario MYWORK-FOCUS-01-1: Focus preserved across filter change
-- GIVEN a filter tab has keyboard focus
-- WHEN the user activates a different filter
-- THEN focus MUST remain on the newly active filter tab
-- AND the focus ring MUST be visible
-
-#### Scenario MYWORK-FOCUS-01-2: Focus visible on item
-- GIVEN an item row receives keyboard focus
-- THEN the row MUST render a clearly visible focus indicator that meets WCAG AA contrast requirements
-- AND the indicator MUST NOT rely on colour alone
-
-#### Scenario MYWORK-FOCUS-01-3: Skip duplicate focusables
-- GIVEN an item that contains nested interactive controls (e.g., a quick-action button)
-- THEN the outer row MUST be focusable as a single stop
-- AND tabbing into the row MUST NOT trap the user in repeated nested stops
-
-### REQ-MYWORK-INT-01: Backwards-Compatible Integration
-The polish layer MUST NOT regress existing "Mijn werk" behaviour.
-
-#### Scenario MYWORK-INT-01-1: Existing sections preserved
-- GIVEN the existing grouped sections, filters, and counts
-- WHEN this change is applied
-- THEN those affordances MUST continue to work unchanged
-- AND no new dependencies on additional stores or APIs MUST be introduced beyond the existing object store
-
-#### Scenario MYWORK-INT-01-2: Single fetch on mount
-- GIVEN the case-type lookup is built on mount
-- THEN the fetch MUST occur at most once per view mount
-- AND subsequent renders MUST reuse the cached lookup
-
-## Dependencies
-- `src/views/MyWork.vue`
-- Shared object store (`createObjectStore('caseType')`)
-- Existing canonical `my-work` capability spec (`openspec/specs/my-work/spec.md`) for the underlying workload, filter, sort, and grouping behaviour
diff --git a/openspec/changes/open-raadsinformatie/specs/open-raadsinformatie/spec.md b/openspec/changes/open-raadsinformatie/specs/open-raadsinformatie/spec.md
index c6c0fb1a..0c9f3ed5 100644
--- a/openspec/changes/open-raadsinformatie/specs/open-raadsinformatie/spec.md
+++ b/openspec/changes/open-raadsinformatie/specs/open-raadsinformatie/spec.md
@@ -14,8 +14,7 @@ Provide case management capabilities for Open Raadsinformatie (ORI) -- the Dutch
**Competitive context**: Existing solutions (Open State Foundation's central aggregator, Argu/OpenRaadsinformatie.nl) are centralized SaaS platforms. This spec enables municipalities to self-host their council information within Nextcloud, retaining data sovereignty while still producing ORI-compliant output for aggregators and open data portals.
-## Requirements
-
+## ADDED Requirements
### Requirement: ORI register MUST be provisionable with all entity schemas
The system MUST provide a pre-configured "Open Raadsinformatie" register containing all ORI entity schemas, deployable via a repair step, CLI command, or admin action. The register template MUST follow the OpenAPI 3.0.0 + `x-openregister` extension pattern used by other mock registers (BRP, KVK, BAG, DSO) and MUST be loadable via the `ConfigurationService -> ImportHandler` pipeline.
diff --git a/openspec/changes/openregister-integration/specs/openregister-integration/spec.md b/openspec/changes/openregister-integration/specs/openregister-integration/spec.md
deleted file mode 100644
index 19da12df..00000000
--- a/openspec/changes/openregister-integration/specs/openregister-integration/spec.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Delta: openregister-integration
-
-## Changes from base spec
-
-### Schema Registration (ENHANCED)
-- Refactored store.js from 13 individual conditional registrations to data-driven pattern
-- Now registers all 27 schemas defined in the spec (configuration + instance + ZGW support)
-- Added: statusRecord, catalogus, zaaktypeInformatieobjecttype, caseProperty, caseDocument,
- caseObject, customerContact, decisionDocument, dispatch, document, documentLink, usageRights,
- kanaal, abonnement
-
-### Frontend Availability Check (NEW)
-- Created `src/utils/openregisterCheck.js` with `checkOpenRegisterStatus()` and `getStatusMessage()`
-- Checks both OpenRegister app availability and Procest register configuration
-- Returns localized status messages for admin guidance
diff --git a/openspec/changes/prometheus-metrics/specs/prometheus-metrics/spec.md b/openspec/changes/prometheus-metrics/specs/prometheus-metrics/spec.md
deleted file mode 100644
index 1f4a8001..00000000
--- a/openspec/changes/prometheus-metrics/specs/prometheus-metrics/spec.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Delta: prometheus-metrics
-
-## Changes from base spec
-
-### REQ-PROM-002a (ENHANCED)
-- Added `nextcloud_version` label to `procest_info` gauge
-
-### REQ-PROM-002b (ENHANCED)
-- `procest_up` gauge now reflects actual database health (was hardcoded to 1)
-
-### REQ-PROM-003e (IMPLEMENTED)
-- Added `procest_cases_created_today` gauge metric
-
-### REQ-PROM-004d (IMPLEMENTED)
-- Added OpenRegister dependency check to health endpoint
-- OpenRegister unavailable sets overall status to "error"
-
-### REQ-PROM-009a/b (IMPLEMENTED)
-- Added APCu caching for all expensive metric queries
-- Case counts and task counts cached for 30 seconds
-- Overdue counts cached for 60 seconds
-- Graceful fallback when APCu is unavailable
diff --git a/openspec/changes/register-i18n/specs/register-i18n/spec.md b/openspec/changes/register-i18n/specs/register-i18n/spec.md
deleted file mode 100644
index 43a47f6f..00000000
--- a/openspec/changes/register-i18n/specs/register-i18n/spec.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Delta: register-i18n
-
-## Changes from base spec
-
-### REQ-I18N-001c/d (IMPLEMENTED - schema flags)
-- Added `x-translatable: true` to translatable properties in procest_register.json
-- caseType: title, description, purpose, trigger, subject
-- statusType: name, description
-- resultType: name, description
-- roleType: name, description
-- documentType: name, description
-- decisionType: name, description
-
-### REQ-I18N-003a (IMPLEMENTED - frontend utility)
-- Created `src/utils/i18nResolver.js` with:
- - `resolveTranslatable(value, locale, fallbackLocale)` — resolves language-tagged fields with fallback chain
- - `getUserLocale()` — gets user's locale from Nextcloud
- - `resolveField(obj, field, locale)` — convenience wrapper
- - `resolveText(obj, field, locale)` — template-friendly wrapper
-- Fallback chain: user locale -> fallback -> nl -> en -> first available
-
-### REQ-I18N-008a/b/c (IMPLEMENTED - glossary)
-- Added 22 glossary terms to l10n/en.json and l10n/nl.json
-- Covers: entity types, status labels, action buttons per the spec glossary tables
diff --git a/openspec/changes/roles-decisions/specs/roles-decisions/spec.md b/openspec/changes/roles-decisions/specs/roles-decisions/spec.md
deleted file mode 100644
index 6565da8b..00000000
--- a/openspec/changes/roles-decisions/specs/roles-decisions/spec.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-status: proposed
----
-
-# roles-decisions Specification (Change Delta)
-
-## Purpose
-Deliver the case-detail UI gaps for Roles & Decisions: a dedicated Decisions section with CRUD and validity-period display, archival-metadata-aware Result section, and field-level role validation. The canonical capability spec lives at `openspec/specs/roles-decisions/spec.md`; this delta narrows scope to the missing UI affordances and the role/decision linkage rules enforced in the frontend.
-
-## Requirements
-
-### Requirement: REQ-DECISION-001 Decisions section MUST support full CRUD from case detail
-The case-detail view SHALL render a `DecisionsSection.vue` component that lists decisions linked to the current case and supports create, edit, and delete operations subject to permissions.
-
-#### Scenario: Authorized role creates a decision
-- GIVEN a case worker with role `handler` or `decision_maker` opens case `ZAAK-2026-000123`
-- WHEN they click "Besluit toevoegen" in the Decisions section
-- THEN a dialog MUST collect `title`, `description`, `decisionType`, `decidedBy`, `decidedAt`, `effectiveDate`, and `expiryDate`
-- AND on save the decision object MUST be persisted via OpenRegister with `case` set to `ZAAK-2026-000123`
-- AND the new decision MUST appear in the section list without a full page reload
-
-#### Scenario: Unauthorized role cannot create a decision
-- GIVEN a user whose only role on the case is `stakeholder`
-- WHEN the case detail loads
-- THEN the "Besluit toevoegen" action MUST be hidden
-- AND the section MUST be read-only
-
-### Requirement: REQ-DECISION-002 Decision validity period MUST be displayed and computed
-Each decision row SHALL show its validity window based on `effectiveDate` and `expiryDate`, computed in `decisionHelpers.js`.
-
-#### Scenario: Decision is currently valid
-- GIVEN a decision with `effectiveDate` 2026-01-01 and `expiryDate` 2026-12-31
-- WHEN the current date is 2026-05-11
-- THEN the row MUST render a green "Geldig" badge with text "Geldig tot 31-12-2026"
-
-#### Scenario: Decision has expired
-- GIVEN a decision with `expiryDate` 2026-04-01
-- WHEN the current date is 2026-05-11
-- THEN the row MUST render a grey "Verlopen" badge with the expiry date
-
-#### Scenario: Decision has no expiry
-- GIVEN a decision with `effectiveDate` set and `expiryDate` empty
-- THEN the badge MUST read "Geldig (onbepaalde tijd)"
-
-### Requirement: REQ-DECISION-005 Decisions section MUST be present on every case detail view
-The Decisions section SHALL appear on `CaseDetail.vue` between the Result section and the activity timeline regardless of whether decisions exist.
-
-#### Scenario: Case has no decisions
-- GIVEN case `ZAAK-2026-000123` has zero decisions
-- THEN the section MUST render its header and an empty-state hint "Nog geen besluiten genomen"
-- AND the create action MUST remain available to authorized roles
-
-### Requirement: REQ-RESULT-001 Result section MUST display archival metadata
-`ResultSection.vue` SHALL surface the archival action and retention information derived from the linked `resultType`.
-
-#### Scenario: Result with retention rule
-- GIVEN the case has a result whose `resultType` has `archiveAction` `retain` and `retentionPeriod` `P20Y`
-- THEN the section MUST display: "Bewaartermijn: 20 jaar", "Archiefactie: bewaren", and the computed retention end date based on `retentionDateSource`
-
-#### Scenario: Result with destroy action
-- GIVEN `archiveAction` is `destroy` with `retentionPeriod` `P5Y`
-- THEN the section MUST display "Archiefactie: vernietigen" with a tooltip explaining the disposal date
-
-### Requirement: REQ-ROLE-006 Role validation MUST enforce field requirements per role type
-When creating or editing a Role on a case, the form SHALL enforce required fields defined by the linked `roleType.genericRole`.
-
-#### Scenario: Initiator requires participant
-- GIVEN the user selects `roleType` whose `genericRole` is `initiator`
-- WHEN they leave `participant` empty and submit
-- THEN the form MUST block submission with error "Een initiator vereist een deelnemer"
-
-#### Scenario: Decision-maker role gates decision creation
-- GIVEN no user holds a role with `genericRole` `decision_maker` on the case
-- WHEN any user attempts to open the "Besluit toevoegen" dialog
-- THEN the dialog MUST display a warning "Geen besluitnemer toegewezen op deze zaak"
-- AND the save action MUST be disabled until a decision_maker role is assigned
-
-### Requirement: REQ-ROLE-007 Role-to-decision audit trail MUST be preserved
-Every decision write SHALL record the actor's role at the moment of the action, so the audit history reflects which role authorized the decision even if the user's roles change later.
-
-#### Scenario: Audit trail on decision create
-- GIVEN user `alice` holds role `decision_maker` on case `ZAAK-2026-000123`
-- WHEN alice creates a decision
-- THEN the decision's audit entry MUST contain `actor: alice`, `actorRole: decision_maker`, and the timestamp
-- AND a later removal of alice's `decision_maker` role MUST NOT alter the historical audit entry
-
-#### Scenario: Audit trail on decision reversal
-- GIVEN an existing decision is deleted by a `coordinator` role
-- THEN the audit entry MUST record `action: delete`, `actor`, `actorRole: coordinator`, and the original decision payload for traceability
diff --git a/openspec/changes/stuf-support/specs/stuf-support/spec.md b/openspec/changes/stuf-support/specs/stuf-support/spec.md
deleted file mode 100644
index c0a9d9c1..00000000
--- a/openspec/changes/stuf-support/specs/stuf-support/spec.md
+++ /dev/null
@@ -1,892 +0,0 @@
----
-status: implemented
----
-# StUF Protocol Support Specification
-
-## Purpose
-
-Procest currently implements ZGW APIs (Zaken, Catalogi, Documenten, Besluiten) for case management via REST controllers (`ZrcController`, `ZtcController`, `DrcController`, `BrcController`). However, many Dutch municipalities still rely on StUF (Standaard Uitwisseling Formaat) -- especially StUF-ZKN (Zaak-Kennis) for case management and StUF-BG (Basis Gemeentelijke) for person/address lookups. This spec defines how Procest supports StUF alongside ZGW, providing a dual API surface over the same OpenRegister case data.
-
-StUF support enables Procest to integrate with legacy form systems (e.g., formulierenmotoren that submit cases via StUF-ZKN), legacy case systems during migration periods, and BRP person lookups via StUF-BG. The approach leverages OpenConnector's existing SOAP infrastructure (SOAPService with StUF-ZKN `edcLk01` awareness) for outbound StUF calls, while adding inbound StUF endpoints to Procest for receiving SOAP messages from legacy consumers.
-
-**Standards**: StUF 3.01, StUF-ZKN 3.10, StUF-BG 3.10, ZGW APIs (VNG), RGBZ, GEMMA
-**Feature tier**: V1 (outbound StUF via OpenConnector), V2 (inbound StUF endpoints in Procest)
-
----
-
-## Architecture Overview
-
-```
- Inbound StUF (V2)
- Legacy systems send SOAP messages to Procest
- ┌──────────────────────────────────┐
- │ Legacy Formulierensysteem │
- │ Legacy Zaaksysteem │
- └────────────┬─────────────────────┘
- │ SOAP (StUF-ZKN/BG)
- ┌────────────▼─────────────────────┐
- │ Procest StUF Controller │
- │ - Raw XML POST handler │
- │ - StUF message parser │
- │ - StUF → OpenRegister mapper │
- └────────────┬─────────────────────┘
- │ Internal API
-┌──────────────────────────────────────▼──────────────────────┐
-│ OpenRegister (procest register) │
-│ - case, caseType, status, role, result, decision schemas │
-│ - Single source of truth for all case data │
-└──────────────────────────────────────▲──────────────────────┘
- │ Internal API
- ┌────────────┴─────────────────────┐
- │ OpenConnector (SOAP outbound) │
- │ - SOAPService (existing) │
- │ - StUF-ZKN/BG message builder │
- │ - mTLS / WS-Security auth │
- └────────────┬─────────────────────┘
- │ SOAP (StUF-ZKN/BG)
- ┌────────────▼─────────────────────┐
- │ External Legacy Services │
- │ - BRP (StUF-BG personen) │
- │ - Legacy zaaksysteem │
- │ - Gemeentelijk DMS │
- └──────────────────────────────────┘
- Outbound StUF (V1)
- Procest/OpenConnector queries legacy systems
-```
-
-### Coexistence Principle
-
-StUF and ZGW share the same underlying OpenRegister data. A case created via StUF-ZKN `zakLk01` is stored in the same `case` schema and is immediately visible via the ZGW Zaken API (`ZrcController`) and the Procest frontend. Conversely, a case created via the ZGW API or UI can be queried via StUF-ZKN `zakLv01`. The translation layer maps between the two representations without duplicating data.
-
----
-
-## Data Model Mapping
-
-### StUF-ZKN to Procest Case Mapping
-
-| StUF-ZKN Field | XML Path | Procest Case Property | ZGW Mapping | Notes |
-|----------------|----------|----------------------|-------------|-------|
-| `zaakidentificatie` | `zakLk01/object/identificatie` | `identifier` | `identificatie` | Auto-generated if not provided |
-| `omschrijving` | `zakLk01/object/omschrijving` | `title` | `omschrijving` | Required |
-| `toelichting` | `zakLk01/object/toelichting` | `description` | `toelichting` | Optional |
-| `startdatum` | `zakLk01/object/startdatum` | `startDate` | `startdatum` | Format: `YYYYMMDD` -> ISO 8601 |
-| `einddatum` | `zakLk01/object/einddatum` | `endDate` | `einddatum` | Format: `YYYYMMDD` -> ISO 8601 |
-| `einddatumGepland` | `zakLk01/object/einddatumGepland` | `plannedEndDate` | `einddatumGepland` | |
-| `uiterlijkeEinddatumAfdoening` | `zakLk01/object/uiterlijkeEinddatumAfdoening` | `deadline` | `uiterlijkeEinddatumAfdoening` | |
-| `isVan/gerelateerde/code` | Nested element | `caseType` (resolved by code) | `zaaktype` | Resolved via caseType lookup |
-| `vertrouwelijkAanduiding` | `zakLk01/object/vertrouwelijkAanduiding` | `confidentiality` | `vertrouwelijkheidaanduiding` | Enum mapping required |
-| `heeftAlsInitiator` | Nested BSN/vestigingsnummer | `role` (genericRole=initiator) | `rol` | Creates a Role object |
-| `heeftAlsBehandelaar` | Nested medewerker | `assignee` + role | `rol` | Handler assignment |
-
-### StUF-ZKN Status Mapping
-
-| StUF-ZKN Field | XML Path | Procest Property | Notes |
-|----------------|----------|-----------------|-------|
-| `datumStatusGezet` | `heeft/datumStatusGezet` | StatusRecord `dateSet` | Format: `YYYYMMDDHHmmss` -> ISO 8601 |
-| `gpiStatusType/code` | Nested element | StatusRecord `statusType` | Resolved via statusType lookup |
-| `statustoelichting` | `heeft/statustoelichting` | StatusRecord `description` | |
-
-### StUF-BG Person Mapping
-
-| StUF-BG Field | XML Path | Description | OpenRegister Property |
-|---------------|----------|-------------|----------------------|
-| `inp.bsn` | `npsLv01/gelijk/inp.bsn` | Burgerservicenummer | `bsn` |
-| `geslachtsnaam` | `npsLa01/antwoord/.../geslachtsnaam` | Family name | `lastName` |
-| `voorvoegselGeslachtsnaam` | Nested element | Name prefix | `namePrefix` |
-| `voornamen` | Nested element | Given names | `firstName` |
-| `geboortedatum` | `inp.geboortedatum` | Date of birth | `dateOfBirth` |
-| `verblijfsadres` | Nested AOA element | Residential address | `address` (composite) |
-| `sub.verblijfBuitenland` | Nested element | Foreign address | `foreignAddress` |
-
-### Confidentiality Enum Mapping
-
-| StUF Value | Procest Value | ZGW Dutch |
-|------------|--------------|-----------|
-| `OPENBAAR` | `public` | openbaar |
-| `BEPERKT OPENBAAR` | `restricted` | beperkt_openbaar |
-| `INTERN` | `internal` | intern |
-| `ZAAKVERTROUWELIJK` | `case_sensitive` | zaakvertrouwelijk |
-| `VERTROUWELIJK` | `confidential` | vertrouwelijk |
-| `CONFIDENTIEEL` | `highly_confidential` | confidentieel |
-| `GEHEIM` | `secret` | geheim |
-| `ZEER GEHEIM` | `top_secret` | zeer_geheim |
-
----
-
-## Requirements
-
----
-
-### REQ-STUF-001: Outbound StUF-BG Person Lookup via OpenConnector
-
-The system MUST support querying external StUF-BG services for person data (BRP) via OpenConnector's SOAP infrastructure. This enables case handlers to look up citizen information by BSN when creating or processing cases.
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-001a: Look up person by BSN
-
-- GIVEN an OpenConnector source configured with type `soap` pointing to a StUF-BG endpoint (e.g., `https://brp.gemeente.nl/StUF/bg0310`)
-- AND the source has valid authentication (mTLS certificate or WS-Security credentials)
-- AND the source has stuurgegevens configured (zender: `Procest`, ontvanger: `BRP`)
-- WHEN a case handler requests person lookup for BSN `999993653`
-- THEN the system MUST construct a StUF-BG `npsLv01` SOAP envelope with the BSN in `gelijk/inp.bsn`
-- AND send it to the configured StUF-BG endpoint via OpenConnector's SOAPService
-- AND parse the `npsLa01` response extracting: `geslachtsnaam`, `voorvoegselGeslachtsnaam`, `voornamen`, `geboortedatum`, `verblijfsadres`
-- AND return the person data as a JSON object to the Procest frontend
-
-#### Scenario STUF-001b: Person not found in BRP
-
-- GIVEN a valid StUF-BG source configuration
-- WHEN a lookup is performed for BSN `000000000` (non-existent)
-- THEN the StUF-BG service returns a `npsLa01` response with zero results (empty `antwoord`)
-- AND the system MUST display: "No person found for BSN 000000000"
-
-#### Scenario STUF-001c: StUF-BG fault handling
-
-- GIVEN a valid StUF-BG source configuration
-- WHEN the StUF service returns a `Fo02` fault message (e.g., `StUF055: Niet geautoriseerd`)
-- THEN the system MUST parse the fault code and fault string from the `Fo02` envelope
-- AND return a structured error: `{ "code": "StUF055", "message": "Niet geautoriseerd", "detail": "..." }`
-- AND log the fault at WARNING level
-
-#### Scenario STUF-001d: Look up person by name and date of birth
-
-- GIVEN a valid StUF-BG source configuration
-- AND the case handler does not have the BSN but has the person's name and date of birth
-- WHEN the handler searches with `geslachtsnaam` = "Moulin" and `geboortedatum` = "19750312"
-- THEN the system MUST construct a `npsLv01` with name and date in `gelijk` elements
-- AND parse the `npsLa01` response which may contain multiple results
-- AND present the results as a selectable list showing BSN, full name, date of birth, and address
-
-#### Scenario STUF-001e: Timeout handling for BRP queries
-
-- GIVEN a valid StUF-BG source configuration with a 10-second timeout
-- WHEN the BRP endpoint does not respond within 10 seconds
-- THEN the system MUST abort the SOAP call and return an error: "BRP service niet beschikbaar (timeout na 10 seconden)"
-- AND log the timeout at WARNING level with the endpoint URL
-- AND the case handler MUST be able to continue case processing without the BRP data
-
----
-
-### REQ-STUF-002: Outbound StUF-ZKN Case Notification
-
-The system MUST support sending case status updates to legacy systems via StUF-ZKN messages. This enables Procest to notify legacy zaaksystemen or DMS systems when case events occur.
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-002a: Send status update notification
-
-- GIVEN an OpenConnector endpoint configured as type `soap` pointing to a legacy zaaksysteem's StUF-ZKN service
-- AND the case "2026-042" has its status changed from "Ontvangen" to "In behandeling"
-- AND the case type is configured with a StUF notification endpoint
-- WHEN the status change is committed
-- THEN the system MUST construct a StUF-ZKN `zakLk01` SOAP envelope with `mutatiesoort="W"` (wijziging)
-- AND include the updated zaak data: `identificatie`, `omschrijving`, `status` (with `datumStatusGezet` and status type code)
-- AND populate `stuurgegevens` with: `zender` (Procest organization code), `ontvanger` (legacy system code), `referentienummer` (UUID), `tijdstipBericht` (current ISO timestamp)
-- AND send the message via OpenConnector's SOAPService
-
-#### Scenario STUF-002b: Send case creation notification
-
-- GIVEN a StUF notification endpoint is configured
-- WHEN a new case "2026-043" is created via the Procest UI or ZGW API
-- THEN the system MUST construct a StUF-ZKN `zakLk01` SOAP envelope with `mutatiesoort="T"` (toevoeging)
-- AND include all initial zaak data including initiator role (`heeftAlsInitiator`)
-- AND send the message via OpenConnector
-
-#### Scenario STUF-002c: Handle notification delivery failure
-
-- GIVEN a StUF notification endpoint is configured but unreachable
-- WHEN a case status change triggers a notification
-- AND the SOAP call fails with a connection timeout
-- THEN the system MUST NOT block the status change (the case update proceeds)
-- AND the system MUST log the delivery failure at ERROR level
-- AND the system SHOULD queue the notification for retry (via OpenConnector's retry mechanism)
-- AND the audit trail MUST record: "StUF notification to [endpoint] failed: connection timeout"
-
-#### Scenario STUF-002d: Send case closure notification
-
-- GIVEN a case "2026-042" with status "In behandeling"
-- AND a result "Toegekend" is set on the case, triggering automatic status transition to "Afgerond"
-- WHEN the closure status is committed
-- THEN the system MUST send a `zakLk01` with `mutatiesoort="W"` containing the final status, result, and end date
-- AND if an archival action date is set, it MUST be included as `archiefactiedatum`
-
-#### Scenario STUF-002e: Selective notification per case type
-
-- GIVEN case type "Omgevingsvergunning" is configured with StUF notification to endpoint A
-- AND case type "Bezwaar" is configured with StUF notification to endpoint B
-- AND case type "Melding" has no StUF notification configured
-- WHEN a status change occurs on a "Melding" case
-- THEN NO StUF notification MUST be sent
-- AND the audit trail MUST NOT contain a StUF notification entry
-
----
-
-### REQ-STUF-003: Outbound StUF-ZKN Document Linking
-
-The system MUST support sending document metadata to legacy DMS systems via StUF-ZKN `edcLk01` messages when documents are uploaded to a case. OpenConnector's SOAPService already has `edcLk01` awareness (base64 content handling).
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-003a: Notify legacy DMS of new document
-
-- GIVEN a case "2026-042" linked to a StUF-ZKN endpoint
-- AND a document "Bouwtekening.pdf" (1.2 MB) is uploaded to the case
-- WHEN the document upload is committed
-- THEN the system MUST construct a StUF-ZKN `edcLk01` SOAP envelope with `mutatiesoort="T"`
-- AND include: `identificatie` (document ID), `titel` ("Bouwtekening.pdf"), `formaat` ("application/pdf"), `inhoud` (base64-encoded content), `vertrouwelijkAanduiding`
-- AND include the zaak reference: `isRelevantVoor/gerelateerde/identificatie` = "2026-042"
-- AND send via OpenConnector's SOAPService (which already handles `edcLk01` content decoding)
-
-#### Scenario STUF-003b: Large document handling
-
-- GIVEN a document larger than 20 MB
-- WHEN the system prepares the `edcLk01` message
-- THEN the system SHOULD use MTOM (Message Transmission Optimization Mechanism) for binary content
-- OR the system MAY skip the `inhoud` element and include only metadata with a download reference
-
-#### Scenario STUF-003c: Document version update notification
-
-- GIVEN a document "Bouwtekening.pdf" previously sent via `edcLk01` with `mutatiesoort="T"`
-- AND the document is replaced with a new version "Bouwtekening_v2.pdf"
-- WHEN the document update is committed
-- THEN the system MUST send a `edcLk01` with `mutatiesoort="W"` (wijziging)
-- AND include the new document content and the same `identificatie` as the original
-- AND include `versie` = "2" to indicate the version number
-
----
-
-### REQ-STUF-004: StUF Stuurgegevens Configuration
-
-The system MUST support configuring StUF `stuurgegevens` (message routing metadata) for each StUF endpoint. Stuurgegevens are mandatory on every StUF message and identify the sender and receiver.
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-004a: Configure stuurgegevens for a StUF source
-
-- GIVEN an admin configuring a new StUF endpoint in OpenConnector
-- WHEN the admin opens the source configuration
-- THEN the form MUST include fields for:
- - `zender.organisatie` (sending organization code, e.g., "0363" for Amsterdam)
- - `zender.applicatie` (sending application name, e.g., "Procest")
- - `ontvanger.organisatie` (receiving organization code)
- - `ontvanger.applicatie` (receiving application name)
-- AND these values MUST be stored with the source configuration
-
-#### Scenario STUF-004b: Stuurgegevens populated on outbound messages
-
-- GIVEN a StUF source with stuurgegevens configured: zender = `{ organisatie: "0363", applicatie: "Procest" }`, ontvanger = `{ organisatie: "0363", applicatie: "BRP" }`
-- WHEN any outbound StUF message is constructed
-- THEN the `stuurgegevens` element MUST contain:
- - `zender/organisatie` = "0363"
- - `zender/applicatie` = "Procest"
- - `ontvanger/organisatie` = "0363"
- - `ontvanger/applicatie` = "BRP"
- - `referentienummer` = newly generated UUID
- - `tijdstipBericht` = current timestamp in `YYYYMMDDHHmmss` format
-
-#### Scenario STUF-004c: Cross-reference in response messages
-
-- GIVEN a StUF source sent a message with `referentienummer` = "550e8400-e29b-41d4-a716-446655440000"
-- WHEN the external service returns a response (La01, Bv01, or Fo01)
-- THEN the response `stuurgegevens` MUST contain `crossRefnummer` matching the original `referentienummer`
-- AND the system MUST validate that `crossRefnummer` matches a known outbound `referentienummer`
-- AND if no match is found, the response MUST be logged at WARNING level and discarded
-
----
-
-### REQ-STUF-005: StUF-ZKN zaakIdentificatie Generation
-
-The system SHALL support the `genereerZaakIdentificatie` service call for obtaining zaak identifiers from external systems that manage identifier sequences.
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-005a: Obtain identifier from legacy system
-
-- GIVEN a StUF-ZKN endpoint that supports `genereerZaakIdentificatie_Di02`
-- AND the case type "Omgevingsvergunning" is configured to use external identifier generation
-- WHEN a new case of this type is being created
-- THEN the system MUST send a `genereerZaakIdentificatie_Di02` message to the configured endpoint
-- AND parse the `genereerZaakIdentificatie_Du02` response to extract the `zaakidentificatie`
-- AND use this identifier as the case's `identifier` instead of auto-generating one
-
-#### Scenario STUF-005b: Fallback to local generation
-
-- GIVEN a case type configured for external identifier generation
-- AND the external StUF endpoint is unreachable
-- WHEN a new case is being created
-- THEN the system MUST fall back to local identifier generation (format: `YYYY-NNN`)
-- AND log a warning: "External zaakidentificatie generation failed, using local identifier"
-
-#### Scenario STUF-005c: Identifier uniqueness validation
-
-- GIVEN a StUF-ZKN endpoint returns identifier "2026-042"
-- AND a case with identifier "2026-042" already exists in OpenRegister
-- WHEN the system processes the `Du02` response
-- THEN the system MUST reject the duplicate identifier
-- AND request a new identifier by sending another `Di02` message
-- AND if 3 consecutive duplicates are returned, fall back to local generation with a UUID-based identifier and log an ERROR
-
----
-
-### REQ-STUF-006: Outbound StUF Authentication
-
-The system MUST support the authentication methods required by Dutch government StUF endpoints. OpenConnector's existing certificate handling and AuthenticationService provide the foundation.
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-006a: mTLS with PKIoverheid certificate
-
-- GIVEN a StUF endpoint requiring PKIoverheid mutual TLS
-- AND the admin has uploaded a client certificate and private key in OpenConnector's source configuration
-- WHEN the system sends a StUF SOAP message
-- THEN the SOAP call MUST include the client certificate for mTLS
-- AND the server's certificate MUST be validated against the PKIoverheid certificate chain
-
-#### Scenario STUF-006b: WS-Security UsernameToken
-
-- GIVEN a StUF endpoint requiring WS-Security UsernameToken authentication
-- AND the admin has configured username and password in the source configuration
-- WHEN the system sends a StUF SOAP message
-- THEN the SOAP envelope Header MUST include a `wsse:Security` element with a `wsse:UsernameToken` containing the configured credentials
-- AND the password SHOULD be sent as `wsse:Password Type="PasswordDigest"` (nonce + timestamp + password hash)
-
-#### Scenario STUF-006c: Certificate renewal warning
-
-- GIVEN a StUF source with a PKIoverheid client certificate expiring in 30 days
-- WHEN an admin views the source configuration
-- THEN the system SHOULD display a warning: "Client certificate expires on [date]. Renew before expiry to prevent service interruption."
-- AND at 7 days before expiry, a Nextcloud notification SHOULD be sent to all admin users
-
-#### Scenario STUF-006d: Certificate chain validation failure
-
-- GIVEN a StUF endpoint with a server certificate signed by a CA not in the PKIoverheid chain
-- WHEN the system attempts to send a SOAP message
-- THEN the SOAP call MUST fail with an error: "Server certificate validation failed: unknown CA"
-- AND the system MUST NOT send the SOAP message content to an untrusted endpoint
-- AND the error MUST be logged at ERROR level with the certificate details
-
----
-
-### REQ-STUF-007: Inbound StUF-ZKN Case Creation
-
-The system MUST accept incoming StUF-ZKN `zakLk01` messages (with `mutatiesoort="T"`) to create cases from legacy form systems or legacy case systems pushing data to Procest. This is the SOAP server challenge -- Nextcloud routes are REST-based, so the inbound StUF endpoint is implemented as a raw POST handler that parses SOAP XML.
-
-**Feature tier**: V2
-
-
-#### Scenario STUF-007a: Receive zakLk01 to create a case
-
-- GIVEN a Procest StUF endpoint exposed at `/apps/procest/api/stuf/zaken`
-- AND the endpoint accepts `POST` with `Content-Type: text/xml` or `application/soap+xml`
-- WHEN a legacy formulierensysteem sends a StUF-ZKN `zakLk01` SOAP message with `mutatiesoort="T"` containing:
- - `omschrijving` = "Aanvraag omgevingsvergunning Dorpsstraat 1"
- - `startdatum` = "20260316"
- - `zaaktype/code` = "OV-001"
- - `heeftAlsInitiator/gerelateerde/inp.bsn` = "999993653"
-- THEN the system MUST parse the SOAP envelope and extract the StUF-ZKN message body
-- AND map `omschrijving` to case `title`
-- AND convert `startdatum` from `YYYYMMDD` to ISO 8601 date
-- AND resolve `zaaktype/code` "OV-001" to the matching Procest case type
-- AND create an OpenRegister object in the `procest` register with the `case` schema
-- AND create a Role object with `genericRole = "initiator"` and BSN `999993653`
-- AND auto-calculate the `deadline` from the case type's `processingDeadline`
-- AND return a StUF `Bv01` (bevestigingsbericht) with the generated `zaakidentificatie`
-
-#### Scenario STUF-007b: Reject zakLk01 with unknown case type
-
-- GIVEN the StUF endpoint receives a `zakLk01` with `zaaktype/code` = "UNKNOWN-001"
-- AND no Procest case type has a matching code
-- WHEN the message is processed
-- THEN the system MUST return a StUF `Fo01` fault message with:
- - `foutcode` = "StUF058"
- - `foutbeschrijving` = "Onbekend zaaktype: UNKNOWN-001"
- - `plek` = "server"
-
-#### Scenario STUF-007c: Reject invalid XML
-
-- GIVEN the StUF endpoint receives a POST with malformed XML
-- WHEN the system attempts to parse the message
-- THEN the system MUST return a SOAP Fault with `faultcode = "Client"` and `faultstring = "Ongeldig XML bericht"`
-
-#### Scenario STUF-007d: Validate stuurgegevens on inbound messages
-
-- GIVEN the StUF endpoint is configured with expected `ontvanger` codes
-- WHEN a `zakLk01` arrives with `ontvanger/applicatie` = "WrongApp"
-- THEN the system MUST return a StUF `Fo01` fault with `foutcode` = "StUF001" and `foutbeschrijving` = "Onbekende ontvanger"
-
-#### Scenario STUF-007e: Case creation with multiple roles
-
-- GIVEN a `zakLk01` with `mutatiesoort="T"` containing:
- - `heeftAlsInitiator/gerelateerde/inp.bsn` = "999993653"
- - `heeftAlsGemachtigde/gerelateerde/vestigingsNummer` = "000012345678"
- - `heeftAlsBelanghebbende/gerelateerde/inp.bsn` = "999990627"
-- WHEN the message is processed
-- THEN the system MUST create three Role objects: initiator (BSN), gemachtigde (vestigingsnummer), belanghebbende (BSN)
-- AND all three roles MUST be linked to the newly created case
-
----
-
-### REQ-STUF-008: Inbound StUF-ZKN Case Query
-
-The system MUST accept incoming StUF-ZKN `zakLv01` (zaak opvragen) messages and respond with `zakLa01` (zaak antwoord) messages containing case data from OpenRegister.
-
-**Feature tier**: V2
-
-
-#### Scenario STUF-008a: Query case by identifier
-
-- GIVEN a case "2026-042" exists in the `procest` register with title "Bouwvergunning Keizersgracht 100", status "In behandeling", startDate "2026-01-15"
-- WHEN a legacy system sends a `zakLv01` with `gelijk/identificatie` = "2026-042"
-- THEN the system MUST return a `zakLa01` SOAP response containing:
- - `identificatie` = "2026-042"
- - `omschrijving` = "Bouwvergunning Keizersgracht 100"
- - `startdatum` = "20260115"
- - Current status with `datumStatusGezet` and status type code
- - Related roles (`heeftAlsInitiator`, `heeftAlsBehandelaar`)
- - Related documents (if `scope` requests them)
-
-#### Scenario STUF-008b: Query with scope filtering
-
-- GIVEN a `zakLv01` request with a `scope` element requesting only `identificatie`, `omschrijving`, and `startdatum`
-- WHEN the system processes the request
-- THEN the `zakLa01` response MUST include only the requested fields
-- AND omitted fields MUST NOT appear in the response (not even as empty elements)
-
-#### Scenario STUF-008c: Query with no results
-
-- GIVEN a `zakLv01` with `gelijk/identificatie` = "9999-999" (non-existent)
-- WHEN the system processes the request
-- THEN the system MUST return a `zakLa01` with an empty `antwoord` element (zero objects)
-
-#### Scenario STUF-008d: Query with maximumAantal
-
-- GIVEN 50 cases matching the query criteria
-- AND the `zakLv01` specifies `maximumAantal` = 10
-- WHEN the system processes the request
-- THEN the `zakLa01` MUST contain at most 10 zaak objects
-
-#### Scenario STUF-008e: Query cases by date range
-
-- GIVEN 20 cases with `startdatum` between "20260101" and "20260331"
-- WHEN a `zakLv01` queries with `van/startdatum` = "20260201" and `totEnMet/startdatum` = "20260228"
-- THEN the `zakLa01` MUST contain only cases with `startdatum` in February 2026
-- AND the response MUST include `stuurgegevens` with `crossRefnummer` matching the query's `referentienummer`
-
----
-
-### REQ-STUF-009: Inbound StUF-ZKN Case Update
-
-The system MUST accept incoming StUF-ZKN `zakLk01` messages with `mutatiesoort="W"` (wijziging) to update existing cases.
-
-**Feature tier**: V2
-
-
-#### Scenario STUF-009a: Update case via zakLk01
-
-- GIVEN a case "2026-042" exists with title "Bouwvergunning Keizersgracht 100"
-- WHEN a legacy system sends a `zakLk01` with `mutatiesoort="W"` and `identificatie` = "2026-042" and `omschrijving` = "Bouwvergunning Keizersgracht 100 - gewijzigd"
-- THEN the system MUST update the case title to "Bouwvergunning Keizersgracht 100 - gewijzigd"
-- AND the audit trail MUST record the update with source "StUF-ZKN"
-- AND the system MUST return a `Bv01` bevestiging
-
-#### Scenario STUF-009b: Update case status via zakLk01
-
-- GIVEN a case "2026-042" at status "Ontvangen"
-- WHEN a `zakLk01` with `mutatiesoort="W"` includes a new status element with `gpiStatusType/code` = "INBEH" and `datumStatusGezet` = "20260316120000"
-- THEN the system MUST resolve "INBEH" to the matching Procest status type
-- AND create a new StatusRecord with the specified date
-- AND all case type validation rules MUST be enforced (required properties, required documents)
-
-#### Scenario STUF-009c: Reject update for non-existent case
-
-- GIVEN no case with identifier "9999-999" exists
-- WHEN a `zakLk01` with `mutatiesoort="W"` and `identificatie` = "9999-999" arrives
-- THEN the system MUST return a `Fo01` fault with `foutcode` = "StUF064" and `foutbeschrijving` = "Zaak niet gevonden: 9999-999"
-
-#### Scenario STUF-009d: Partial update with only changed fields
-
-- GIVEN a case "2026-042" with title, description, startDate, and deadline set
-- WHEN a `zakLk01` with `mutatiesoort="W"` includes only `toelichting` = "Aangepaste toelichting"
-- THEN the system MUST update only the `description` field
-- AND all other fields (title, startDate, deadline) MUST remain unchanged
-- AND the `Bv01` response MUST confirm the update
-
-#### Scenario STUF-009e: Reject update on closed case
-
-- GIVEN a case "2026-042" with status "Afgerond" (closed)
-- WHEN a `zakLk01` with `mutatiesoort="W"` attempts to change the `omschrijving`
-- THEN the system MUST enforce the `ZgwZrcRulesService` immutability rules
-- AND return a `Fo01` fault with `foutcode` = "StUF062" and `foutbeschrijving` = "Zaak is afgesloten en kan niet meer worden gewijzigd"
-
----
-
-### REQ-STUF-010: Inbound StUF-ZKN Document Handling
-
-The system MUST accept incoming StUF-ZKN `edcLk01` messages to link documents to cases.
-
-**Feature tier**: V2
-
-
-#### Scenario STUF-010a: Receive document via edcLk01
-
-- GIVEN a case "2026-042" exists
-- WHEN a legacy system sends an `edcLk01` with `mutatiesoort="T"` containing:
- - `identificatie` = "DOC-2026-001"
- - `titel` = "Bouwtekening.pdf"
- - `formaat` = "application/pdf"
- - `inhoud` = base64-encoded PDF content
- - `isRelevantVoor/gerelateerde/identificatie` = "2026-042"
-- THEN the system MUST decode the base64 `inhoud`
-- AND store the document in Nextcloud Files under the case's folder
-- AND create a caseDocument object linking the document to the case
-- AND return a `Bv01` bevestiging
-
-#### Scenario STUF-010b: Document without content (metadata only)
-
-- GIVEN an `edcLk01` with document metadata but no `inhoud` element
-- WHEN the message is processed
-- THEN the system MUST create a caseDocument object with the metadata
-- AND mark the document as "metadata only -- no content received"
-
-#### Scenario STUF-010c: Document linked to non-existent case
-
-- GIVEN an `edcLk01` with `isRelevantVoor/gerelateerde/identificatie` = "9999-999"
-- AND no case with that identifier exists
-- WHEN the message is processed
-- THEN the system MUST return a `Fo01` fault with `foutcode` = "StUF064" and `foutbeschrijving` = "Zaak niet gevonden: 9999-999"
-
----
-
-### REQ-STUF-011: StUF XML Message Processing
-
-The system MUST correctly handle StUF XML namespaces, date formats, noValue attributes, and message structure.
-
-**Feature tier**: V1 (outbound), V2 (inbound)
-
-
-#### Scenario STUF-011a: XML namespace handling
-
-- GIVEN a StUF-ZKN message is being constructed or parsed
-- THEN the system MUST correctly handle these namespaces:
- - `http://www.egem.nl/StUF/StUF0301` (StUF base)
- - `http://www.egem.nl/StUF/sector/zkn/0310` (StUF-ZKN)
- - `http://www.egem.nl/StUF/sector/bg/0310` (StUF-BG)
- - `http://www.w3.org/2001/XMLSchema-instance` (xsi)
- - `http://www.opengis.net/gml` (gml, for geometry)
-
-#### Scenario STUF-011b: Date format conversion
-
-- GIVEN a date value "2026-03-16" in ISO 8601 format (Procest internal)
-- WHEN the value is included in an outbound StUF message
-- THEN the value MUST be converted to StUF format: "20260316"
-- AND datetime values MUST use format: "YYYYMMDDHHmmss" (e.g., "20260316143000")
-
-#### Scenario STUF-011c: noValue attribute handling
-
-- GIVEN a case property that has no value (null/empty)
-- WHEN the property is included in an outbound StUF message
-- THEN the element MUST include the appropriate `StUF:noValue` attribute:
- - `geenWaarde` -- explicitly set to no value
- - `waardeOnbekend` -- value exists but is unknown
- - `nietOndersteund` -- field not supported by the system
- - `vastgesteldOnbekend` -- officially determined as unknown
-
-#### Scenario STUF-011d: Validate outbound XML against XSD
-
-- GIVEN bundled StUF-ZKN 3.10 and StUF-BG 3.10 XSD schemas
-- WHEN an outbound StUF message is constructed
-- THEN the system SHOULD validate the XML against the relevant XSD before sending
-- AND if validation fails, the system MUST log the validation errors and NOT send the invalid message
-
-#### Scenario STUF-011e: Handle XML entities and special characters
-
-- GIVEN a case title containing special characters: `Vergunning "Café & Bar" `
-- WHEN the title is included in an outbound StUF message
-- THEN XML entities MUST be properly escaped: `&`, `<`, `>`, `"`
-- AND the resulting XML MUST be well-formed and parseable
-
----
-
-### REQ-STUF-012: StUF-BG Inbound Person Query
-
-The system SHALL accept incoming StUF-BG `npsLv01` messages to expose person data stored in OpenRegister. This enables legacy systems to query Procest as if it were a BRP source.
-
-**Feature tier**: V2
-
-
-#### Scenario STUF-012a: Receive person query
-
-- GIVEN the Procest StUF endpoint at `/apps/procest/api/stuf/personen`
-- AND a person object with BSN "999993653" exists in OpenRegister
-- WHEN a legacy system sends a StUF-BG `npsLv01` with `gelijk/inp.bsn` = "999993653"
-- THEN the system MUST return a `npsLa01` response with the person data mapped from OpenRegister to StUF-BG XML
-
-#### Scenario STUF-012b: Person query with scope
-
-- GIVEN a `npsLv01` with scope requesting only `inp.bsn`, `geslachtsnaam`, and `geboortedatum`
-- WHEN the system processes the request
-- THEN the `npsLa01` MUST include only the requested fields
-
-#### Scenario STUF-012c: Person query with wildcard search
-
-- GIVEN 5 persons in OpenRegister with `geslachtsnaam` starting with "Jan"
-- WHEN a `npsLv01` queries with `gelijk/geslachtsnaam` = "Jan*" (wildcard)
-- THEN the system MUST return all 5 matching persons in the `npsLa01` response
-- AND results MUST be ordered by `geslachtsnaam` ascending
-
----
-
-### REQ-STUF-013: StUF Field Mapping Configuration
-
-The system MUST store field mappings between StUF XML paths and OpenRegister object properties as configurable mapping objects. Default mappings for ZGW-zaak and BRP-person data MUST be pre-seeded.
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-013a: Pre-seeded zaak field mapping
-
-- GIVEN the Procest app is installed and the repair step runs
-- THEN a default StUF-ZKN field mapping MUST be created in OpenRegister containing mappings for all fields listed in the "StUF-ZKN to Procest Case Mapping" table above
-- AND the mapping MUST include date format transformations (StUF `YYYYMMDD` to ISO 8601 and vice versa)
-
-#### Scenario STUF-013b: Custom field mapping
-
-- GIVEN a municipality uses a StUF extension with custom fields (e.g., `gem:kenmerk` for a local reference number)
-- WHEN an admin adds a custom mapping entry: StUF path `gem:kenmerk` -> OpenRegister property `localReference`
-- THEN the system MUST apply this mapping during StUF message parsing and construction
-- AND the custom mapping MUST NOT override default mappings unless explicitly configured
-
-#### Scenario STUF-013c: Value transformation in mapping
-
-- GIVEN a mapping entry for `vertrouwelijkAanduiding` with a value transformation table (StUF enum values to Procest enum values)
-- WHEN a StUF message contains `vertrouwelijkAanduiding` = "ZAAKVERTROUWELIJK"
-- THEN the system MUST transform the value to `case_sensitive` using the mapping's transformation table
-
-#### Scenario STUF-013d: Mapping object schema structure
-
-- GIVEN the StUF field mapping is stored as an OpenRegister object
-- THEN the mapping schema MUST include:
- - `name` (string): mapping set name (e.g., "StUF-ZKN to Procest Case")
- - `sourceFormat` (enum): "stuf-zkn" | "stuf-bg"
- - `targetSchema` (string): OpenRegister schema reference (e.g., "case", "person")
- - `fieldMappings` (array): each entry with `{ stufPath, openRegisterProperty, dataType, transformation, required }`
- - `dateFormat` (string): date format pattern for this mapping set
- - `isDefault` (boolean): whether this is a system-provided mapping
-
-#### Scenario STUF-013e: Export and import mapping configurations
-
-- GIVEN an admin who configured custom StUF mappings for municipality A
-- WHEN the admin exports the mapping configuration
-- THEN the system MUST produce a JSON file containing all custom mapping entries
-- AND the exported file MUST be importable on another Procest instance for municipality B
-
----
-
-### REQ-STUF-014: SOAP Server Within Nextcloud
-
-The system SHALL provide a SOAP server within Nextcloud for exposing inbound StUF endpoints. Since Nextcloud routes are REST-based, the StUF controller accepts raw XML POSTs and processes them as SOAP messages without using PHP's built-in SoapServer (which requires WSDL mode and conflicts with Nextcloud's routing).
-
-**Feature tier**: V2
-
-
-#### Scenario STUF-014a: Raw SOAP POST handling
-
-- GIVEN a Procest route `/apps/procest/api/stuf/{service}` registered as a raw POST endpoint
-- WHEN a SOAP message arrives with `Content-Type: text/xml; charset=utf-8`
-- THEN the controller MUST read the raw request body (`php://input`)
-- AND parse the SOAP envelope using PHP's DOMDocument or SimpleXML
-- AND extract the SOAP Body content
-- AND dispatch to the appropriate handler based on the root element name (e.g., `zakLk01`, `zakLv01`, `npsLv01`)
-- AND construct a SOAP envelope response with the appropriate content
-- AND return with `Content-Type: text/xml; charset=utf-8`
-
-#### Scenario STUF-014b: WSDL serving
-
-- GIVEN bundled WSDL files for StUF-ZKN and StUF-BG services
-- WHEN a client sends a GET request to `/apps/procest/api/stuf/zaken?wsdl`
-- THEN the system MUST return the StUF-ZKN WSDL file with `Content-Type: text/xml`
-- AND the WSDL's `soap:address location` MUST reflect the actual Procest endpoint URL
-
-#### Scenario STUF-014c: SOAPAction header routing
-
-- GIVEN a SOAP request with `SOAPAction: "http://www.egem.nl/StUF/sector/zkn/0310/zakLk01"`
-- WHEN the controller processes the request
-- THEN the SOAPAction header MAY be used as a secondary dispatch mechanism alongside XML body inspection
-
-#### Scenario STUF-014d: Request logging for audit compliance
-
-- GIVEN the Procest StUF endpoint receives a SOAP message
-- THEN the system MUST log: timestamp, source IP, SOAPAction header, message type (zakLk01/zakLv01/etc.), stuurgegevens (zender/ontvanger), processing result (success/fault)
-- AND the raw XML MUST be stored in a separate audit log (configurable retention period)
-- AND sensitive data (BSN, personal names) MUST be masked in standard log output but preserved in the audit log
-
----
-
-### REQ-STUF-015: Dual API Coexistence
-
-The system MUST ensure that StUF and ZGW APIs provide consistent views of the same data. Changes made via one protocol MUST be immediately visible via the other.
-
-**Feature tier**: V1
-
-
-#### Scenario STUF-015a: Case created via StUF visible in ZGW
-
-- GIVEN a case created via inbound StUF-ZKN `zakLk01` with identifier "2026-042"
-- WHEN a ZGW API client sends GET `/api/v1/zaken?identificatie=2026-042` to the ZrcController
-- THEN the case MUST be returned with all fields populated from the StUF-created data
-- AND the `url` field MUST contain the ZGW-style self-link
-
-#### Scenario STUF-015b: Case created via ZGW queryable via StUF
-
-- GIVEN a case created via the ZGW Zaken API with identifier "2026-043"
-- WHEN a legacy system sends a StUF-ZKN `zakLv01` with `gelijk/identificatie` = "2026-043"
-- THEN the case MUST be returned in the `zakLa01` response with all fields correctly mapped to StUF-ZKN XML
-
-#### Scenario STUF-015c: Case updated via UI reflected in StUF query
-
-- GIVEN a case "2026-042" with status "Ontvangen"
-- WHEN a handler changes the status to "In behandeling" via the Procest frontend
-- AND a legacy system immediately sends a `zakLv01` for "2026-042"
-- THEN the response MUST show the new status "In behandeling" with the correct `datumStatusGezet`
-
-#### Scenario STUF-015d: StUF and ZGW audit trail unification
-
-- GIVEN a case "2026-042" that was:
- 1. Created via StUF-ZKN `zakLk01`
- 2. Updated via ZGW Zaken API
- 3. Queried via StUF-ZKN `zakLv01`
-- WHEN viewing the case audit trail in the Procest frontend
-- THEN all three actions MUST appear in chronological order
-- AND each entry MUST show the protocol used ("StUF-ZKN", "ZGW API", "Frontend")
-- AND the audit trail MUST be queryable by protocol type
-
----
-
-## SOAP Server Challenge
-
-Hosting a SOAP server within Nextcloud presents architectural challenges:
-
-1. **Nextcloud routing is REST-based**: All routes go through `routes.php` and expect JSON request/response. StUF requires raw XML POST handling with SOAP envelope wrapping.
-
-2. **PHP SoapServer limitations**: PHP's built-in `SoapServer` class operates in WSDL mode and expects to handle the full HTTP lifecycle. Within Nextcloud's controller framework, this conflicts with the existing request/response handling.
-
-3. **Proposed solution**: Implement a `StufController` that extends `OCP\AppFramework\Controller` and:
- - Registers routes for `/api/stuf/zaken` and `/api/stuf/personen`
- - Reads raw XML from `php://input`
- - Parses XML using DOMDocument (not SoapServer)
- - Dispatches to a `StufMessageHandler` service
- - Constructs SOAP XML responses manually
- - Returns `DataDisplayResponse` with `Content-Type: text/xml`
-
-4. **Alternative approach**: Use OpenConnector as a SOAP proxy -- OpenConnector could host the SOAP endpoint (it already has SOAPService) and forward parsed data to Procest's REST API. This avoids the SOAP-in-Nextcloud problem but adds a network hop and dependency.
-
----
-
-## Dependencies
-
-- **OpenConnector stuf-adapter spec** (`openconnector/openspec/specs/stuf-adapter/spec.md`): Provides the SOAP infrastructure (SOAPService), certificate handling, and source type configuration. Procest leverages OpenConnector for all outbound StUF communication.
-- **OpenRegister**: All case data stored as objects; field mapping configurations stored as OpenRegister objects.
-- **Procest case-management spec** (`../case-management/spec.md`): The case data model, status lifecycle, validation rules, and audit trail that StUF messages map to/from.
-- **Procest roles-decisions spec** (`../roles-decisions/spec.md`): Role entities created from StUF `heeftAlsInitiator`/`heeftAlsBehandelaar` data.
-- **Procest openregister-integration spec** (`../openregister-integration/spec.md`): The `procest` register and 12 schemas that store all data.
-- **PHP DOMDocument / SimpleXML**: For XML parsing and construction (bundled with PHP).
-- **StUF XSD schema packages**: StUF-BG 3.10 and StUF-ZKN 3.10 XSD files for validation.
-- **Existing ZGW controllers** (`ZrcController`, `ZtcController`, `DrcController`, `BrcController`): The REST API surface that coexists with StUF.
-
----
-
-## Current Implementation Status
-
-### Using Mock Register Data
-
-This spec depends on the **BRP** mock register for testing StUF-BG person lookup (REQ-STUF-001) and the **BAG** mock register for address data.
-
-**Loading the registers:**
-```bash
-# Load BRP register (35 persons, register slug: "brp", schema: "ingeschreven-persoon")
-docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json
-
-# Load BAG register (32 addresses + 21 objects + 21 buildings, register slug: "bag")
-docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/bag_register.json
-```
-
-**Test data for this spec's use cases:**
-- **StUF-BG npsLv01 person lookup**: BSN `999993653` (Suzanne Moulin) -- test outbound BRP query and npsLa01 response mapping
-- **StUF-BG person not found**: BSN `000000000` -- test empty npsLa01 response handling
-- **StUF-ZKN with initiator BSN**: BSN `999990627` (Stephan Janssen) -- test inbound zakLk01 with `heeftAlsInitiator/inp.bsn`
-- **StUF-BG inbound query (REQ-STUF-012)**: BSN `999992570` (Albert Vogel) -- test serving person data via StUF-BG endpoint
-
-### Procest
-
-**No StUF implementation exists.** Grep for "stuf", "StUF", and "soap" in `procest/lib/` returns zero results. All current API communication is via ZGW REST APIs through the four ZGW controllers.
-
-**Implemented (ZGW foundation that StUF maps to):**
-- ZGW Zaken API via `ZrcController` -- zaken, statussen, resultaten, rollen, zaakeigenschappen, zaakinformatieobjecten, zaakobjecten, klantcontacten
-- ZGW Catalogi API via `ZtcController` -- zaaktypen, statustypen, resultaattypen, roltypen, besluittypen
-- ZGW Documenten API via `DrcController` -- enkelvoudiginformatieobjecten
-- ZGW Besluiten API via `BrcController` -- besluiten
-- ZGW business rules via `ZgwBusinessRulesService` and `ZgwZrcRulesService`
-- OpenRegister schemas for all 12 entity types
-
-### OpenConnector
-
-**Partial SOAP infrastructure exists** (see `openconnector/openspec/specs/stuf-adapter/spec.md` for details):
-- `SOAPService` with generic SOAP client, WSDL-driven requests, SOAP 1.1/1.2 support
-- Specific StUF-ZKN `edcLk01` handling (base64 document content decoding)
-- Source type `soap` with WSDL URL and authentication configuration
-- Certificate handling for mTLS (PKIoverheid)
-- `CallService` SOAP routing (type `soap` -> SOAPService)
-- **NOT implemented in OpenConnector**: Inbound SOAP server, StUF field mappings, WSDL bundling, stuurgegevens, namespace handling, noValue attributes, fault message handling (Fo01/Fo02/Fo03/Bv03)
-
----
-
-## Standards & References
-
-- **StUF 3.01**: Standaard Uitwisseling Formaat, base standard. Defines the SOAP message structure, stuurgegevens, kennisgevingen (Lk01), vraag/antwoord (Lv01/La01), bevestiging (Bv01/Bv03), and foutmeldingen (Fo01/Fo02/Fo03). Maintained by VNG Realisatie. https://www.gemmaonline.nl/index.php/StUF_Berichtenstandaard
-- **StUF-ZKN 3.10**: Sectormodel Zaak-/Documentservices, built on StUF 3.01. Defines zaak (zak), document (edc), status, and related message types. The "e" extension (3.10e) adds extra message types. https://www.gemmaonline.nl/index.php/Sectormodel_Zaken:_StUF-ZKN
-- **StUF-BG 3.10**: Sectormodel Basisgegevens, built on StUF 3.01. Defines person (nps), address (adr), and other base registry data types. https://www.gemmaonline.nl/index.php/Sectormodel_Basisgegevens:_StUF-BG
-- **RGBZ 2.0 (Referentiemodel Gemeentelijke Basisgegevens Zaken)**: The information model underlying StUF-ZKN, defining zaak, status, document, besluit, and their relationships. The same model underlies ZGW APIs.
-- **ZGW APIs (VNG)**: The modern REST-based successor to StUF-ZKN. Procest already implements these via ZrcController, ZtcController, DrcController, BrcController. https://vng-realisatie.github.io/gemma-zaken/
-- **GEMMA**: Gemeentelijke Model Architectuur, the reference architecture for Dutch municipalities. Defines how StUF and ZGW fit in the municipal information landscape. https://www.gemmaonline.nl/
-- **Logius**: Dutch government IT authority responsible for PKIoverheid certificates and DigiKoppeling (the transport standard for government-to-government communication, which mandates how StUF messages are exchanged).
-- **DigiKoppeling**: Transport standard (WUS/ebMS) for Dutch government interoperability. StUF messages are typically exchanged over DigiKoppeling WUS (SOAP+WS-Security). https://www.logius.nl/domeinen/gegevensuitwisseling/digikoppeling
-- **WS-Security (OASIS)**: SOAP message security standard. UsernameToken and X.509 Token profiles are used by Dutch government StUF endpoints.
-- **PKIoverheid**: Dutch government PKI for mTLS authentication on production StUF endpoints.
-- **MTOM (Message Transmission Optimization Mechanism)**: W3C standard for efficient binary content in SOAP messages, relevant for large document transfers via edcLk01.
-
----
-
-## Specificity Assessment
-
-### Sufficient for implementation
-- Complete field mapping tables between StUF-ZKN/BG and Procest case model
-- Clear separation of outbound (V1, via OpenConnector) and inbound (V2, SOAP server) concerns
-- Detailed Gherkin scenarios for each message type with concrete data examples
-- Explicit SOAP server architecture proposal addressing the Nextcloud routing challenge
-- Coexistence principle ensuring data consistency between StUF and ZGW APIs
-- Authentication methods (mTLS, WS-Security) aligned with existing OpenConnector capabilities
-- Reference to OpenConnector's existing SOAPService and edcLk01 handling
-
-### Missing or ambiguous
-- **StUF version negotiation**: The spec targets 3.01/3.10 but some municipalities may run older versions. Version detection and fallback behavior is not defined.
-- **Async response patterns**: StUF supports asynchronous Bv03/Fo03 callback patterns for long-running operations. The callback mechanism (how Procest receives async responses) is not detailed.
-- **Performance requirements**: No throughput or latency SLAs for SOAP message processing.
-- **Mapping object schema**: REQ-STUF-013 now defines the mapping schema structure in scenario STUF-013d.
-- **Multi-source routing**: Can multiple StUF endpoints be configured for different case types, or is there one global StUF endpoint?
-- **StUF-ZKN 3.10e extensions**: The "e" extension adds extra message types not covered in this spec.
-- **Archival-related StUF messages**: StUF defines messages for archival transfers (overbrenging) which relate to the case result archival rules but are not covered here.
-- **Rate limiting and access control**: How are inbound StUF endpoints secured beyond stuurgegevens validation? IP whitelisting? Client certificate validation on the inbound side?
-
-### Open questions
-1. Should inbound StUF endpoints be hosted in Procest directly or proxied via OpenConnector (which already has SOAPService)?
-2. Which StUF version(s) must be supported on day one -- 3.01 only, 3.10 only, or both?
-3. Should the field mapping configuration be stored in the `procest` register or in OpenConnector's register?
-4. How should large document content in inbound `edcLk01` messages be handled -- streamed to disk or loaded into memory?
-5. Is DigiKoppeling (WUS profile) compliance required, or is plain HTTPS with WS-Security sufficient?
diff --git a/openspec/changes/task-management/specs/task-management/spec.md b/openspec/changes/task-management/specs/task-management/spec.md
deleted file mode 100644
index 1baef0b9..00000000
--- a/openspec/changes/task-management/specs/task-management/spec.md
+++ /dev/null
@@ -1,135 +0,0 @@
----
-status: implemented
----
-# task-management Specification
-
-## Purpose
-Define the MVP task surface in Procest: validated task creation against a parent case, lifecycle state transitions with explicit error feedback, a filterable and searchable task list, overdue highlighting, anatomy of task cards, and reliable completion-date handling. Tasks are first-class work items linked to cases and surfaced both in `TaskList.vue`/`TaskDetail.vue` and in the `my-work` view.
-
-## Context
-Procest tasks are OpenRegister objects with a parent case reference, an assignee, a status (`new`/`in-progress`/`blocked`/`done`/`cancelled`), an optional due date, and a priority. Earlier changes shipped the schema and the basic CRUD; this change closes MVP gaps in validation, lifecycle feedback, list ergonomics, and card anatomy.
-
-## Requirements
-
-### REQ-TASK-VAL-01: Case Reference Validation on Task Creation
-Task creation MUST validate that the parent case reference exists and is usable before persisting the task.
-
-#### Scenario TASK-VAL-01-1: Missing case reference
-- GIVEN a user opens `TaskCreateDialog.vue` and submits without selecting a case
-- WHEN `taskValidation.js` runs
-- THEN the form MUST highlight the case field with the error "Selecteer een zaak"
-- AND save MUST be blocked
-
-#### Scenario TASK-VAL-01-2: Unknown case reference
-- GIVEN a user submits a task referencing a case id that does not resolve via the object store
-- THEN validation MUST fail with "Zaak niet gevonden"
-- AND no task object MUST be persisted
-
-#### Scenario TASK-VAL-01-3: Required core fields
-- GIVEN a task with no title
-- THEN validation MUST fail with "Titel is verplicht" before any backend call is made
-
-### REQ-TASK-LC-01: Lifecycle Transition Error Feedback
-`TaskDetail.vue` MUST surface explicit error messages when a lifecycle status transition is rejected.
-
-#### Scenario TASK-LC-01-1: Invalid transition
-- GIVEN a task currently in status `done`
-- WHEN the user attempts to set the status back to `new`
-- THEN the UI MUST display a Dutch-language error explaining that the transition is not allowed
-- AND the task status MUST remain unchanged
-
-#### Scenario TASK-LC-01-2: Backend rejection surfaced
-- GIVEN the backend returns a 4xx error on a status update
-- THEN the error message from the response MUST be surfaced near the status control
-- AND the status select MUST revert to its previous value
-
-#### Scenario TASK-LC-01-3: Transition success clears prior errors
-- WHEN a transition succeeds after a prior failure
-- THEN any previously displayed transition error MUST be cleared
-
-### REQ-TASK-LIST-01: Task List Filters and Search
-`TaskList.vue` MUST expose filters for status, assignee, and priority, plus keyword search.
-
-#### Scenario TASK-LIST-01-1: Filter by status
-- GIVEN the task list contains tasks in multiple statuses
-- WHEN the user selects a status from the status filter
-- THEN the list MUST refresh with `_filters[status]` applied
-- AND only matching tasks MUST be visible
-
-#### Scenario TASK-LIST-01-2: Filter by assignee
-- WHEN the user selects an assignee from the assignee filter
-- THEN the list MUST refresh with `_filters[assignee]` applied
-
-#### Scenario TASK-LIST-01-3: Filter by priority
-- WHEN the user selects a priority value
-- THEN the list MUST refresh with `_filters[priority]` applied
-
-#### Scenario TASK-LIST-01-4: Keyword search
-- GIVEN a task with title "Documenten verzamelen voor advies"
-- WHEN the user types "advies" into the search field
-- THEN the list MUST refresh with `_search=advies` and the matching task MUST appear
-
-### REQ-TASK-LIST-02: Overdue Highlighting
-`TaskList.vue` MUST visually highlight overdue tasks.
-
-#### Scenario TASK-LIST-02-1: Overdue row styling
-- GIVEN a task with a `dueDate` in the past and a non-final status
-- THEN the row MUST render with an explicit overdue visual treatment (red accent or icon)
-- AND the due date MUST be labeled "Te laat" or equivalent Dutch term
-
-#### Scenario TASK-LIST-02-2: Completed tasks not flagged
-- GIVEN a task that is in status `done` or `cancelled` even with a past `dueDate`
-- THEN no overdue styling MUST be applied
-
-### REQ-TASK-CARD-01: Task Card Anatomy
-Task cards in both the list and the my-work view MUST display a consistent, recognizable anatomy.
-
-#### Scenario TASK-CARD-01-1: Required card content
-- GIVEN any task card
-- THEN the card MUST display: title, parent case reference (identifier + truncated case title), assignee, priority indicator, status pill, and due date
-
-#### Scenario TASK-CARD-01-2: Missing case reference
-- GIVEN a task whose parent case can not be resolved at render time
-- THEN the case reference area MUST show a neutral placeholder rather than an empty space, without breaking the layout
-
-#### Scenario TASK-CARD-01-3: Clickable card navigates to task detail
-- WHEN the user clicks anywhere on the card (outside interactive controls)
-- THEN the app MUST navigate to the task detail view
-
-### REQ-TASK-COMP-01: Completion Date on Completion
-Tasks transitioning to `done` MUST have their `completedDate` set automatically.
-
-#### Scenario TASK-COMP-01-1: Auto-set completedDate
-- GIVEN a task transitioning from any status to `done`
-- WHEN the update is persisted
-- THEN the task object MUST contain a `completedDate` set to the transition timestamp
-- AND the value MUST be set even if the client did not explicitly send it
-
-#### Scenario TASK-COMP-01-2: Re-opening clears completion
-- GIVEN a task previously in `done`
-- WHEN the user reopens it via an allowed transition (e.g., to `in-progress`)
-- THEN `completedDate` MUST be cleared on the persisted object
-
-#### Scenario TASK-COMP-01-3: Cancelled tasks
-- GIVEN a task transitioning to `cancelled`
-- THEN `completedDate` MUST NOT be set (only `done` transitions trigger it)
-
-### REQ-TASK-INT-01: Integration with Case Detail and My Work
-Task changes MUST stay coherent with the case detail and my-work surfaces.
-
-#### Scenario TASK-INT-01-1: Reflected on case detail
-- GIVEN a task linked to a case
-- WHEN the task is created, updated, or completed
-- THEN the case detail tasks panel MUST reflect the change on next render
-
-#### Scenario TASK-INT-01-2: Reflected in my-work
-- GIVEN a task assigned to the current user
-- WHEN the task transitions, becomes overdue, or completes
-- THEN the my-work view MUST reflect the change on next render and re-apply its grouping
-
-## Dependencies
-- OpenRegister `task` schema and shared `createObjectStore`
-- `src/views/tasks/TaskList.vue`, `src/views/tasks/TaskDetail.vue`, `src/views/tasks/TaskCreateDialog.vue`
-- `src/utils/taskValidation.js`
-- `case-management` capability (parent case references)
-- `my-work` capability (task surface for assigned tasks)
diff --git a/openspec/changes/vth-module/specs/vth-module/spec.md b/openspec/changes/vth-module/specs/vth-module/spec.md
index 7ddc500e..625fe348 100644
--- a/openspec/changes/vth-module/specs/vth-module/spec.md
+++ b/openspec/changes/vth-module/specs/vth-module/spec.md
@@ -106,372 +106,363 @@ adviesAanvraag:
receivedAt: datetime
```
-## Requirements
-
+## ADDED Requirements
---
-### REQ-VTH-01: DSO/Omgevingsloket Integration
+### Requirement: REQ-VTH-01 — DSO/Omgevingsloket Integration
The system MUST support receiving vergunningaanvragen from the Digitaal Stelsel Omgevingswet (DSO) and creating cases from them.
**Feature tier**: V1
+#### Scenario: DSO application creates permit case
-#### Scenario VTH-01a: DSO application creates permit case
-
-- GIVEN DSO integration is configured via OpenConnector
-- WHEN a citizen submits an omgevingsvergunning aanvraag via the Omgevingsloket
-- AND the DSO forwards the verzoek to the municipality's VTH system
-- THEN the system MUST create a case of type "Omgevingsvergunning Bouwactiviteit"
-- AND the case MUST include: activiteiten (from DSO), locatie (BAG/kadaster), aanvrager gegevens, bijlagen
-- AND bouwkosten from the DSO verzoek MUST be stored as a case property (used for legesberekening)
-- AND the case deadline MUST be calculated per the Omgevingswet procedure type (regulier: 8 weeks, uitgebreid: 26 weeks)
-- AND the case MUST be automatically assigned to the configured team for this case type
+- **GIVEN** DSO integration is configured via OpenConnector
+- **WHEN** a citizen submits an omgevingsvergunning aanvraag via the Omgevingsloket
+- **AND** the DSO forwards the verzoek to the municipality's VTH system
+- **THEN** the system MUST create a case of type "Omgevingsvergunning Bouwactiviteit"
+- **AND** the case MUST include: activiteiten (from DSO), locatie (BAG/kadaster), aanvrager gegevens, bijlagen
+- **AND** bouwkosten from the DSO verzoek MUST be stored as a case property (used for legesberekening)
+- **AND** the case deadline MUST be calculated per the Omgevingswet procedure type (regulier: 8 weeks, uitgebreid: 26 weeks)
+- **AND** the case MUST be automatically assigned to the configured team for this case type
-#### Scenario VTH-01b: Multiple activities in single application
+#### Scenario: Multiple activities in single application
-- GIVEN a DSO verzoek with 3 activities: "Bouwen", "Kappen", "Uitrit aanleggen"
-- WHEN the case is created
-- THEN the system MUST register all 3 activities on the case as case properties
-- AND each activity MAY trigger a separate toets/advies task
-- AND legesberekening MUST consider samenloop (combined activities, see legesberekening spec)
+- **GIVEN** a DSO verzoek with 3 activities: "Bouwen", "Kappen", "Uitrit aanleggen"
+- **WHEN** the case is created
+- **THEN** the system MUST register all 3 activities on the case as case properties
+- **AND** each activity MAY trigger a separate toets/advies task
+- **AND** legesberekening MUST consider samenloop (combined activities, see legesberekening spec)
-#### Scenario VTH-01c: DSO status updates
+#### Scenario: DSO status updates
-- GIVEN a case created from a DSO verzoek
-- WHEN the case status changes (e.g., from "Ontvangen" to "In behandeling")
-- THEN the system MUST push a status update back to the DSO via OpenConnector
-- AND the aanvrager MUST be able to see the current status in the Omgevingsloket
+- **GIVEN** a case created from a DSO verzoek
+- **WHEN** the case status changes (e.g., from "Ontvangen" to "In behandeling")
+- **THEN** the system MUST push a status update back to the DSO via OpenConnector
+- **AND** the aanvrager MUST be able to see the current status in the Omgevingsloket
-#### Scenario VTH-01d: DSO aanvulverzoek (request for additional information)
+#### Scenario: DSO aanvulverzoek (request for additional information)
-- GIVEN a case where the ontvankelijkheidstoets reveals missing documents
-- WHEN the behandelaar sends an aanvulverzoek via the case dashboard
-- THEN the system MUST push the aanvulverzoek to the DSO
-- AND the case deadline MUST be suspended (opgeschort) during the aanvulperiode
-- AND the suspension MUST be recorded in the case timeline
+- **GIVEN** a case where the ontvankelijkheidstoets reveals missing documents
+- **WHEN** the behandelaar sends an aanvulverzoek via the case dashboard
+- **THEN** the system MUST push the aanvulverzoek to the DSO
+- **AND** the case deadline MUST be suspended (opgeschort) during the aanvulperiode
+- **AND** the suspension MUST be recorded in the case timeline
-#### Scenario VTH-01e: DSO intake validation
+#### Scenario: DSO intake validation
-- GIVEN a DSO verzoek arrives with invalid or incomplete data
-- WHEN the system attempts to create a case
-- THEN validation errors MUST be logged: "DSO verzoek [id]: ontbrekend veld 'bouwkosten'"
-- AND the case MUST still be created (with incomplete data) for manual completion
-- AND a task MUST be created for the behandelaar: "DSO intake validatie: controleer ontbrekende gegevens"
+- **GIVEN** a DSO verzoek arrives with invalid or incomplete data
+- **WHEN** the system attempts to create a case
+- **THEN** validation errors MUST be logged: "DSO verzoek [id]: ontbrekend veld 'bouwkosten'"
+- **AND** the case MUST still be created (with incomplete data) for manual completion
+- **AND** a task MUST be created for the behandelaar: "DSO intake validatie: controleer ontbrekende gegevens"
---
-### REQ-VTH-02: Ontvankelijkheidstoets (Completeness Check)
+### Requirement: REQ-VTH-02 — Ontvankelijkheidstoets (Completeness Check)
The system MUST support a structured completeness check for permit applications, using document type checklists per case type.
**Feature tier**: V1
+#### Scenario: Incomplete application detected
-#### Scenario VTH-02a: Incomplete application detected
-
-- GIVEN a case "Omgevingsvergunning Bouw" with document checklist requiring: bouwtekening, constructieberekening, situatietekening, welstandsadvies, foto's bestaande situatie
-- AND only bouwtekening and foto's have been uploaded
-- WHEN the behandelaar performs the ontvankelijkheidstoets via the case dashboard
-- THEN the system MUST display which required documents are missing (3 of 5)
-- AND each missing document MUST be highlighted with a warning icon
-- AND the system MUST support sending an "Aanvulverzoek" to the aanvrager (via DSO or email)
+- **GIVEN** a case "Omgevingsvergunning Bouw" with document checklist requiring: bouwtekening, constructieberekening, situatietekening, welstandsadvies, foto's bestaande situatie
+- **AND** only bouwtekening and foto's have been uploaded
+- **WHEN** the behandelaar performs the ontvankelijkheidstoets via the case dashboard
+- **THEN** the system MUST display which required documents are missing (3 of 5)
+- **AND** each missing document MUST be highlighted with a warning icon
+- **AND** the system MUST support sending an "Aanvulverzoek" to the aanvrager (via DSO or email)
-#### Scenario VTH-02b: Complete application
+#### Scenario: Complete application
-- GIVEN all required documents have been uploaded (5 of 5)
-- WHEN the behandelaar performs the ontvankelijkheidstoets
-- THEN the system MUST display "Ontvankelijk" with all checkmarks green
-- AND the system MUST allow the behandelaar to confirm ontvankelijkheid and advance the case status
+- **GIVEN** all required documents have been uploaded (5 of 5)
+- **WHEN** the behandelaar performs the ontvankelijkheidstoets
+- **THEN** the system MUST display "Ontvankelijk" with all checkmarks green
+- **AND** the system MUST allow the behandelaar to confirm ontvankelijkheid and advance the case status
-#### Scenario VTH-02c: Deadline suspension during aanvulperiode
+#### Scenario: Deadline suspension during aanvulperiode
-- GIVEN a case with deadline of 8 weeks from start date 2026-03-01 (deadline: 2026-04-26)
-- AND the behandelaar sends an aanvulverzoek on 2026-03-10
-- AND the aanvrager responds with additional documents on 2026-03-25
-- WHEN the behandelaar marks the aanvulverzoek as completed
-- THEN the case deadline MUST be recalculated: 15 days were suspended, new deadline: 2026-05-11
-- AND the suspension period MUST be recorded in the case timeline and deadline panel
+- **GIVEN** a case with deadline of 8 weeks from start date 2026-03-01 (deadline: 2026-04-26)
+- **AND** the behandelaar sends an aanvulverzoek on 2026-03-10
+- **AND** the aanvrager responds with additional documents on 2026-03-25
+- **WHEN** the behandelaar marks the aanvulverzoek as completed
+- **THEN** the case deadline MUST be recalculated: 15 days were suspended, new deadline: 2026-05-11
+- **AND** the suspension period MUST be recorded in the case timeline and deadline panel
-#### Scenario VTH-02d: Aanvulverzoek timeout
+#### Scenario: Aanvulverzoek timeout
-- GIVEN an aanvulverzoek was sent with a 4-week response deadline
-- AND the aanvrager has not responded within 4 weeks
-- WHEN the timeout is reached
-- THEN a task MUST be created for the behandelaar: "Aanvulverzoek verlopen: beoordeel of aanvraag buiten behandeling wordt gesteld"
-- AND the teamleider MUST receive a notification
+- **GIVEN** an aanvulverzoek was sent with a 4-week response deadline
+- **AND** the aanvrager has not responded within 4 weeks
+- **WHEN** the timeout is reached
+- **THEN** a task MUST be created for the behandelaar: "Aanvulverzoek verlopen: beoordeel of aanvraag buiten behandeling wordt gesteld"
+- **AND** the teamleider MUST receive a notification
---
-### REQ-VTH-03: Inspection Checklists
+### Requirement: REQ-VTH-03 — Inspection Checklists
The system MUST support configurable inspection checklists per case type or inspection type, stored as `inspectieChecklist` objects in OpenRegister.
**Feature tier**: V1
+#### Scenario: Configure inspection checklist
-#### Scenario VTH-03a: Configure inspection checklist
-
-- GIVEN an admin configuring checklist "Bouwtoezicht fase 1 - Fundering" with items:
+- **GIVEN** an admin configuring checklist "Bouwtoezicht fase 1 - Fundering" with items:
- "Fundering conform tekening" (ja/nee/nvt + toelichting)
- "Wapening aanwezig en correct" (ja/nee/nvt + toelichting)
- "Waterkering conform bestek" (ja/nee/nvt + toelichting + foto verplicht)
- "Maatvoering gecontroleerd" (ja/nee/nvt + getal: afwijking in mm)
-- WHEN the checklist is saved
-- THEN the checklist MUST be linked to case type "Toezichtzaak Bouw"
-- AND each item MUST support: pass/fail/not-applicable, free text comment, optional photo requirement, optional numeric measurement
-- AND the checklist MUST be versioned (v1, v2, ...) so in-progress inspections use their original version
+- **WHEN** the checklist is saved
+- **THEN** the checklist MUST be linked to case type "Toezichtzaak Bouw"
+- **AND** each item MUST support: pass/fail/not-applicable, free text comment, optional photo requirement, optional numeric measurement
+- **AND** the checklist MUST be versioned (v1, v2, ...) so in-progress inspections use their original version
-#### Scenario VTH-03b: Complete inspection checklist
+#### Scenario: Complete inspection checklist
-- GIVEN an inspector performing "Bouwtoezicht fase 1" on case "2026-089"
-- WHEN the inspector fills in all checklist items and marks 2 items as "nee" (failed)
-- THEN the system MUST record an `inspectieRapport`: inspector name, date/time, location, result per item
-- AND the overall result MUST be automatically determined: "Niet-conform" (2 failed items)
-- AND the completed rapport MUST be stored as a document on the case
-- AND a summary task MUST be generated: "Opvolging vereist: 2 afwijkingen geconstateerd"
+- **GIVEN** an inspector performing "Bouwtoezicht fase 1" on case "2026-089"
+- **WHEN** the inspector fills in all checklist items and marks 2 items as "nee" (failed)
+- **THEN** the system MUST record an `inspectieRapport`: inspector name, date/time, location, result per item
+- **AND** the overall result MUST be automatically determined: "Niet-conform" (2 failed items)
+- **AND** the completed rapport MUST be stored as a document on the case
+- **AND** a summary task MUST be generated: "Opvolging vereist: 2 afwijkingen geconstateerd"
-#### Scenario VTH-03c: Photo capture during inspection
+#### Scenario: Photo capture during inspection
-- GIVEN a checklist item "Waterkering conform bestek" with fotoRequired=true
-- WHEN the inspector marks this item as "nee" (failed)
-- THEN the system MUST require at least one photo before the rapport can be submitted
-- AND photos MUST be stored in Nextcloud Files under the case folder
-- AND each photo MUST be linked to the specific checklist item
+- **GIVEN** a checklist item "Waterkering conform bestek" with fotoRequired=true
+- **WHEN** the inspector marks this item as "nee" (failed)
+- **THEN** the system MUST require at least one photo before the rapport can be submitted
+- **AND** photos MUST be stored in Nextcloud Files under the case folder
+- **AND** each photo MUST be linked to the specific checklist item
-#### Scenario VTH-03d: Multiple inspections per case
+#### Scenario: Multiple inspections per case
-- GIVEN a bouwtoezicht case with 3 inspection phases: fundering, ruwbouw, oplevering
-- WHEN the inspector completes "fase 1 - fundering"
-- THEN the completed rapport MUST be stored and the next inspection ("fase 2 - ruwbouw") MUST become available
-- AND the case dashboard MUST show inspection progress: "Inspectie 1/3 voltooid"
+- **GIVEN** a bouwtoezicht case with 3 inspection phases: fundering, ruwbouw, oplevering
+- **WHEN** the inspector completes "fase 1 - fundering"
+- **THEN** the completed rapport MUST be stored and the next inspection ("fase 2 - ruwbouw") MUST become available
+- **AND** the case dashboard MUST show inspection progress: "Inspectie 1/3 voltooid"
-#### Scenario VTH-03e: Inspection history
+#### Scenario: Inspection history
-- GIVEN a case with 3 completed inspection rapporten
-- WHEN the behandelaar views the case dashboard
-- THEN the "Inspecties" panel MUST show all 3 rapporten: date, inspector, result (conform/niet-conform), failed item count
-- AND each rapport MUST be expandable to view individual checklist item results
+- **GIVEN** a case with 3 completed inspection rapporten
+- **WHEN** the behandelaar views the case dashboard
+- **THEN** the "Inspecties" panel MUST show all 3 rapporten: date, inspector, result (conform/niet-conform), failed item count
+- **AND** each rapport MUST be expandable to view individual checklist item results
---
-### REQ-VTH-04: Enforcement Strategies (Handhaving)
+### Requirement: REQ-VTH-04 — Enforcement Strategies (Handhaving)
The system SHALL support the Landelijke Handhavingsstrategie (LHS) for determining appropriate enforcement actions.
**Feature tier**: V2
+#### Scenario: LHS matrix application
-#### Scenario VTH-04a: LHS matrix application
-
-- GIVEN a constatering of type "Overtreding bouwvergunning"
-- AND the inspector classifies: ernst = "aanzienlijk", gedrag = "onverschillig"
-- WHEN the LHS matrix is applied via the enforcement wizard
-- THEN the system MUST suggest the appropriate interventie: "Last onder dwangsom + proces-verbaal"
-- AND the behandelaar MAY override the suggestion with documented reasoning
-- AND the override MUST be recorded in the audit trail
+- **GIVEN** a constatering of type "Overtreding bouwvergunning"
+- **AND** the inspector classifies: ernst = "aanzienlijk", gedrag = "onverschillig"
+- **WHEN** the LHS matrix is applied via the enforcement wizard
+- **THEN** the system MUST suggest the appropriate interventie: "Last onder dwangsom + proces-verbaal"
+- **AND** the behandelaar MAY override the suggestion with documented reasoning
+- **AND** the override MUST be recorded in the audit trail
-#### Scenario VTH-04b: Enforcement workflow
+#### Scenario: Enforcement workflow
-- GIVEN a handhavingsbesluit "Last onder dwangsom" with begunstigingstermijn of 6 weeks
-- AND dwangsom bedrag EUR 5,000 per overtreding, maximaal EUR 25,000
-- WHEN the begunstigingstermijn expires
-- THEN the system MUST create a follow-up task: "Hercontrole uitvoeren"
-- AND if the overtreding persists, the system MUST support: verbeuring dwangsom, effectuering bestuursdwang
-- AND each verbeuring MUST be recorded with amount and date
+- **GIVEN** a handhavingsbesluit "Last onder dwangsom" with begunstigingstermijn of 6 weeks
+- **AND** dwangsom bedrag EUR 5,000 per overtreding, maximaal EUR 25,000
+- **WHEN** the begunstigingstermijn expires
+- **THEN** the system MUST create a follow-up task: "Hercontrole uitvoeren"
+- **AND** if the overtreding persists, the system MUST support: verbeuring dwangsom, effectuering bestuursdwang
+- **AND** each verbeuring MUST be recorded with amount and date
-#### Scenario VTH-04c: Vooraankondiging workflow
+#### Scenario: Vooraankondiging workflow
-- GIVEN a constatering requiring enforcement
-- WHEN the behandelaar initiates a vooraankondiging
-- THEN the system MUST:
+- **GIVEN** a constatering requiring enforcement
+- **WHEN** the behandelaar initiates a vooraankondiging
+- **THEN** the system MUST:
1. Generate a vooraankondigingsbrief via Docudesk template
2. Set a zienswijzetermijn (typically 2 weeks)
3. Create a task: "Beoordeel zienswijze na termijn"
-- AND if a zienswijze is received, it MUST be recorded on the case
-- AND the behandelaar MUST assess the zienswijze before proceeding to handhavingsbesluit
+- **AND** if a zienswijze is received, it MUST be recorded on the case
+- **AND** the behandelaar MUST assess the zienswijze before proceeding to handhavingsbesluit
-#### Scenario VTH-04d: LHS matrix configuration
+#### Scenario: LHS matrix configuration
-- GIVEN the beheerder configures the LHS matrix in admin settings
-- WHEN setting up the ernst x gedrag matrix
-- THEN the system MUST support the standard 4x4 matrix:
+- **GIVEN** the beheerder configures the LHS matrix in admin settings
+- **WHEN** setting up the ernst x gedrag matrix
+- **THEN** the system MUST support the standard 4x4 matrix:
| | Goedwillend | Onverschillig | Calculerend | Crimineel |
|---|---|---|---|---|
| Gering | Waarschuwing | Waarschuwing + herstel | Last onder dwangsom | PV + Last |
| Aanzienlijk | Herstel | Last onder dwangsom | Last + PV | PV + Bestuursdwang |
| Ernstig | Last onder dwangsom | Last + PV | PV + Bestuursdwang | PV + Bestuursdwang |
-- AND the matrix MUST be customizable per municipality
+- **AND** the matrix MUST be customizable per municipality
---
-### REQ-VTH-05: Supervision Planning (Toezichtplan)
+### Requirement: REQ-VTH-05 — Supervision Planning (Toezichtplan)
The system SHALL support creating and managing annual supervision plans.
**Feature tier**: V2
+#### Scenario: Annual inspection planning
-#### Scenario VTH-05a: Annual inspection planning
-
-- GIVEN a toezichtplan for 2026 with 150 planned inspections across categories:
+- **GIVEN** a toezichtplan for 2026 with 150 planned inspections across categories:
- Horeca: 40 inspections
- Bouw: 60 inspections
- Milieu: 50 inspections
-- WHEN the planner views the toezichtplan dashboard
-- THEN the system MUST show: planned vs. completed per category, capacity vs. demand, completion percentage
-- AND a progress bar per category MUST indicate completion: Horeca (15/40 = 38%), Bouw (22/60 = 37%), Milieu (18/50 = 36%)
+- **WHEN** the planner views the toezichtplan dashboard
+- **THEN** the system MUST show: planned vs. completed per category, capacity vs. demand, completion percentage
+- **AND** a progress bar per category MUST indicate completion: Horeca (15/40 = 38%), Bouw (22/60 = 37%), Milieu (18/50 = 36%)
-#### Scenario VTH-05b: Risk-based inspection scheduling
+#### Scenario: Risk-based inspection scheduling
-- GIVEN objects with risk profiles (high/medium/low) based on previous inspection results
-- WHEN generating the toezichtplan
-- THEN high-risk objects MUST be scheduled more frequently (e.g., every 6 months)
-- AND medium-risk objects: annually
-- AND low-risk objects: every 2 years
-- AND the system SHOULD suggest an optimal inspection schedule based on available inspector capacity
+- **GIVEN** objects with risk profiles (high/medium/low) based on previous inspection results
+- **WHEN** generating the toezichtplan
+- **THEN** high-risk objects MUST be scheduled more frequently (e.g., every 6 months)
+- **AND** medium-risk objects: annually
+- **AND** low-risk objects: every 2 years
+- **AND** the system SHOULD suggest an optimal inspection schedule based on available inspector capacity
-#### Scenario VTH-05c: Geographic clustering
+#### Scenario: Geographic clustering
-- GIVEN 20 planned inspections across the municipality
-- WHEN the planner views the inspection map
-- THEN inspections MUST be plottable on a map (using BAG coordinates)
-- AND the system SHOULD suggest geographic clusters for efficient routing
+- **GIVEN** 20 planned inspections across the municipality
+- **WHEN** the planner views the inspection map
+- **THEN** inspections MUST be plottable on a map (using BAG coordinates)
+- **AND** the system SHOULD suggest geographic clusters for efficient routing
-#### Scenario VTH-05d: Actual vs. planned tracking
+#### Scenario: Actual vs. planned tracking
-- GIVEN a toezichtplan with 40 planned horeca inspections for Q1
-- AND 25 have been completed, 5 have been cancelled
-- WHEN viewing the quarterly overview
-- THEN the system MUST show: planned (40), completed (25), cancelled (5), remaining (10)
-- AND a forecast MUST indicate whether the annual target is achievable at current pace
+- **GIVEN** a toezichtplan with 40 planned horeca inspections for Q1
+- **AND** 25 have been completed, 5 have been cancelled
+- **WHEN** viewing the quarterly overview
+- **THEN** the system MUST show: planned (40), completed (25), cancelled (5), remaining (10)
+- **AND** a forecast MUST indicate whether the annual target is achievable at current pace
---
-### REQ-VTH-06: Advice Management (Advies)
+### Requirement: REQ-VTH-06 — Advice Management (Advies)
The system MUST support requesting and tracking internal and external advice on permit applications.
**Feature tier**: V1
+#### Scenario: Request internal advice from specialist
-#### Scenario VTH-06a: Request internal advice from specialist
+- **GIVEN** a case "Omgevingsvergunning Bouw" requiring welstandsadvies
+- **WHEN** the behandelaar clicks "Advies aanvragen" and selects the welstandscommissie (internal)
+- **THEN** an `adviesAanvraag` MUST be created: type=intern, adviseur=welstandscommissie, deadline=[configured default]
+- **AND** a task MUST be created for the welstandscommissie: "Welstandsadvies uitbrengen voor [case identifier]"
+- **AND** the task MUST include: deadline, relevant case documents, specific questions
+- **AND** the case timeline MUST show: "Advies aangevraagd bij Welstandscommissie"
-- GIVEN a case "Omgevingsvergunning Bouw" requiring welstandsadvies
-- WHEN the behandelaar clicks "Advies aanvragen" and selects the welstandscommissie (internal)
-- THEN an `adviesAanvraag` MUST be created: type=intern, adviseur=welstandscommissie, deadline=[configured default]
-- AND a task MUST be created for the welstandscommissie: "Welstandsadvies uitbrengen voor [case identifier]"
-- AND the task MUST include: deadline, relevant case documents, specific questions
-- AND the case timeline MUST show: "Advies aangevraagd bij Welstandscommissie"
+#### Scenario: External advice via ketenpartner
-#### Scenario VTH-06b: External advice via ketenpartner
+- **GIVEN** a case requiring advice from the brandweer (Veiligheidsregio)
+- **WHEN** the behandelaar requests external advice
+- **THEN** the system MUST support sending the request via email (with case documents attached) or ZGW API
+- **AND** the adviesAanvraag MUST be created with type=extern and a deadline
+- **AND** a reminder notification MUST be generated 3 days before the deadline
+- **AND** late advice MUST trigger an escalation notification to the behandelaar and teamleider
-- GIVEN a case requiring advice from the brandweer (Veiligheidsregio)
-- WHEN the behandelaar requests external advice
-- THEN the system MUST support sending the request via email (with case documents attached) or ZGW API
-- AND the adviesAanvraag MUST be created with type=extern and a deadline
-- AND a reminder notification MUST be generated 3 days before the deadline
-- AND late advice MUST trigger an escalation notification to the behandelaar and teamleider
+#### Scenario: Receive and process advice
-#### Scenario VTH-06c: Receive and process advice
+- **GIVEN** an adviesAanvraag is pending for the welstandscommissie
+- **WHEN** the adviseur uploads the advies document and marks the request as "Ontvangen"
+- **THEN** the adviesAanvraag status MUST change to "Ontvangen"
+- **AND** the advies document MUST be linked to the case
+- **AND** the behandelaar MUST be notified: "Welstandsadvies ontvangen voor [case identifier]"
+- **AND** the case timeline MUST show: "Welstandsadvies ontvangen" with a link to the document
-- GIVEN an adviesAanvraag is pending for the welstandscommissie
-- WHEN the adviseur uploads the advies document and marks the request as "Ontvangen"
-- THEN the adviesAanvraag status MUST change to "Ontvangen"
-- AND the advies document MUST be linked to the case
-- AND the behandelaar MUST be notified: "Welstandsadvies ontvangen voor [case identifier]"
-- AND the case timeline MUST show: "Welstandsadvies ontvangen" with a link to the document
+#### Scenario: Advice overview on case dashboard
-#### Scenario VTH-06d: Advice overview on case dashboard
+- **GIVEN** a case with 3 adviesAanvragen: welstandscommissie (ontvangen), brandweer (aangevraagd, deadline in 5 days), milieuadvies (verlopen, 2 days overdue)
+- **WHEN** viewing the case dashboard
+- **THEN** an "Adviezen" panel MUST show all 3 with: adviseur, status badge (green/orange/red), deadline
+- **AND** overdue advice MUST be highlighted in red with days overdue
-- GIVEN a case with 3 adviesAanvragen: welstandscommissie (ontvangen), brandweer (aangevraagd, deadline in 5 days), milieuadvies (verlopen, 2 days overdue)
-- WHEN viewing the case dashboard
-- THEN an "Adviezen" panel MUST show all 3 with: adviseur, status badge (green/orange/red), deadline
-- AND overdue advice MUST be highlighted in red with days overdue
+#### Scenario: Advice deadline tracking
-#### Scenario VTH-06e: Advice deadline tracking
-
-- GIVEN 5 open adviesAanvragen across multiple cases
-- AND 2 are overdue
-- WHEN the teamleider views the werkvoorraad
-- THEN overdue advice requests MUST appear as a separate filter option: "Verlopen adviezen (2)"
-- AND each overdue request MUST show: case reference, adviseur, days overdue
+- **GIVEN** 5 open adviesAanvragen across multiple cases
+- **AND** 2 are overdue
+- **WHEN** the teamleider views the werkvoorraad
+- **THEN** overdue advice requests MUST appear as a separate filter option: "Verlopen adviezen (2)"
+- **AND** each overdue request MUST show: case reference, adviseur, days overdue
---
-### REQ-VTH-07: Permit Procedure Deadlines
+### Requirement: REQ-VTH-07 — Permit Procedure Deadlines
The system MUST enforce Omgevingswet procedure deadlines per permit type and support suspension and extension.
**Feature tier**: V1
+#### Scenario: Reguliere procedure deadline
-#### Scenario VTH-07a: Reguliere procedure deadline
-
-- GIVEN a case type "Omgevingsvergunning Bouwactiviteit" with procedure "regulier"
-- AND the Omgevingswet reguliere beslistermijn is 8 weeks
-- WHEN the case is created with startdatum 2026-03-01
-- THEN the case deadline MUST be automatically set to 2026-04-26 (8 weeks)
-- AND the deadline MUST be displayed in the case dashboard's deadline panel
+- **GIVEN** a case type "Omgevingsvergunning Bouwactiviteit" with procedure "regulier"
+- **AND** the Omgevingswet reguliere beslistermijn is 8 weeks
+- **WHEN** the case is created with startdatum 2026-03-01
+- **THEN** the case deadline MUST be automatically set to 2026-04-26 (8 weeks)
+- **AND** the deadline MUST be displayed in the case dashboard's deadline panel
-#### Scenario VTH-07b: Uitgebreide procedure deadline
+#### Scenario: Uitgebreide procedure deadline
-- GIVEN a case requiring "uitgebreide procedure" (e.g., milieu-impact)
-- AND the Omgevingswet uitgebreide beslistermijn is 26 weeks
-- WHEN the case is created
-- THEN the case deadline MUST be set to startdatum + 26 weeks
+- **GIVEN** a case requiring "uitgebreide procedure" (e.g., milieu-impact)
+- **AND** the Omgevingswet uitgebreide beslistermijn is 26 weeks
+- **WHEN** the case is created
+- **THEN** the case deadline MUST be set to startdatum + 26 weeks
-#### Scenario VTH-07c: Extension (verlenging)
+#### Scenario: Extension (verlenging)
-- GIVEN a case with reguliere procedure deadline approaching
-- AND the case type allows one extension of 6 weeks
-- WHEN the behandelaar requests an extension via the deadline panel
-- THEN the deadline MUST be extended by 6 weeks
-- AND the extension MUST be communicated to the aanvrager (via DSO status update)
-- AND the case timeline MUST record: "Beslistermijn verlengd met 6 weken. Reden: [reden]"
+- **GIVEN** a case with reguliere procedure deadline approaching
+- **AND** the case type allows one extension of 6 weeks
+- **WHEN** the behandelaar requests an extension via the deadline panel
+- **THEN** the deadline MUST be extended by 6 weeks
+- **AND** the extension MUST be communicated to the aanvrager (via DSO status update)
+- **AND** the case timeline MUST record: "Beslistermijn verlengd met 6 weken. Reden: [reden]"
-#### Scenario VTH-07d: Lex silencio positivo (van rechtswege)
+#### Scenario: Lex silencio positivo (van rechtswege)
-- GIVEN a case where the deadline has passed without a decision
-- AND the case type is subject to lex silencio positivo
-- WHEN the deadline expires
-- THEN the system MUST create an urgent alert: "WAARSCHUWING: Vergunning van rechtswege verleend op [deadline datum]"
-- AND a task MUST be created for the teamleider: "Beoordeel vergunning van rechtswege"
-- AND this scenario MUST be preventable by the deadline monitoring in the werkvoorraad spec
+- **GIVEN** a case where the deadline has passed without a decision
+- **AND** the case type is subject to lex silencio positivo
+- **WHEN** the deadline expires
+- **THEN** the system MUST create an urgent alert: "WAARSCHUWING: Vergunning van rechtswege verleend op [deadline datum]"
+- **AND** a task MUST be created for the teamleider: "Beoordeel vergunning van rechtswege"
+- **AND** this scenario MUST be preventable by the deadline monitoring in the werkvoorraad spec
---
-### REQ-VTH-08: VTH Case Type Templates
+### Requirement: REQ-VTH-08 — VTH Case Type Templates
The system MUST provide pre-configured case type templates for common VTH processes.
**Feature tier**: V1
+#### Scenario: Omgevingsvergunning template
-#### Scenario VTH-08a: Omgevingsvergunning template
-
-- GIVEN the beheerder wants to set up VTH case types
-- WHEN importing the "Omgevingsvergunning Bouwactiviteit" template
-- THEN the system MUST create:
+- **GIVEN** the beheerder wants to set up VTH case types
+- **WHEN** importing the "Omgevingsvergunning Bouwactiviteit" template
+- **THEN** the system MUST create:
- Case type with correct processing deadline (8 weeks regulier)
- Status types: Ontvangen, Ontvankelijkheidstoets, In behandeling, Advies, Besluitvorming, Afgehandeld
- Document types: bouwtekening, constructieberekening, situatietekening, welstandsadvies, foto's
- Property definitions: bouwkosten, oppervlakte, aantal bouwlagen, BAG-object
- Role types: behandelaar, aanvrager, gemachtigde, adviseur
-#### Scenario VTH-08b: Toezichtzaak template
+#### Scenario: Toezichtzaak template
-- GIVEN the beheerder imports the "Toezichtzaak Bouw" template
-- THEN the system MUST create:
+- **GIVEN** the beheerder imports the "Toezichtzaak Bouw" template
+- **THEN** the system MUST create:
- Case type with inspection phases (fundering, ruwbouw, oplevering)
- Inspection checklists per phase
- Status types: Gepland, In uitvoering, Rapport, Opvolging, Afgehandeld
- Role types: inspecteur, contactpersoon, opdrachtgever
-#### Scenario VTH-08c: Handhavingszaak template
+#### Scenario: Handhavingszaak template
-- GIVEN the beheerder imports the "Handhavingszaak" template
-- THEN the system MUST create:
+- **GIVEN** the beheerder imports the "Handhavingszaak" template
+- **THEN** the system MUST create:
- Case type with enforcement phases
- Status types: Constatering, Vooraankondiging, Zienswijze, Handhavingsbesluit, Begunstigingstermijn, Hercontrole, Afgehandeld
- Property definitions: overtredingstype, ernst, gedrag, interventie, dwangsombedrag, begunstigingstermijn
@@ -479,102 +470,99 @@ The system MUST provide pre-configured case type templates for common VTH proces
---
-### REQ-VTH-09: Bekendmaking (Publication)
+### Requirement: REQ-VTH-09 — Bekendmaking (Publication)
The system MUST support publishing permit decisions in accordance with the Omgevingswet publication requirements.
**Feature tier**: V1
+#### Scenario: Publish permit decision
-#### Scenario VTH-09a: Publish permit decision
+- **GIVEN** a case with a definitief besluit (vergunning verleend)
+- **WHEN** the behandelaar triggers bekendmaking
+- **THEN** the system MUST generate a bekendmakingstekst using a Docudesk template
+- **AND** the publicatiedatum and bezwaartermijn MUST be calculated
+- **AND** the bekendmaking data MUST be exportable to the Gemeentelijk Publicatieplatform or DROP (Decentrale Regelgeving en Officiële Publicaties)
-- GIVEN a case with a definitief besluit (vergunning verleend)
-- WHEN the behandelaar triggers bekendmaking
-- THEN the system MUST generate a bekendmakingstekst using a Docudesk template
-- AND the publicatiedatum and bezwaartermijn MUST be calculated
-- AND the bekendmaking data MUST be exportable to the Gemeentelijk Publicatieplatform or DROP (Decentrale Regelgeving en Officiële Publicaties)
+#### Scenario: Bezwaartermijn tracking
-#### Scenario VTH-09b: Bezwaartermijn tracking
+- **GIVEN** a published besluit with a bezwaartermijn of 6 weeks starting from publicatiedatum
+- **WHEN** the bezwaartermijn expires
+- **THEN** the system MUST create a task: "Bezwaartermijn verlopen: [case identifier]"
+- **AND** if no bezwaar is received, the besluit becomes onherroepelijk
+- **AND** the case timeline MUST record: "Besluit onherroepelijk geworden"
-- GIVEN a published besluit with a bezwaartermijn of 6 weeks starting from publicatiedatum
-- WHEN the bezwaartermijn expires
-- THEN the system MUST create a task: "Bezwaartermijn verlopen: [case identifier]"
-- AND if no bezwaar is received, the besluit becomes onherroepelijk
-- AND the case timeline MUST record: "Besluit onherroepelijk geworden"
+#### Scenario: Bezwaar received
-#### Scenario VTH-09c: Bezwaar received
-
-- GIVEN a published besluit within the bezwaartermijn
-- WHEN a bezwaar is received
-- THEN the system MUST support creating a new case linked to the original: type "Bezwaarprocedure"
-- AND the original case MUST show: "Bezwaar ontvangen: [datum]"
+- **GIVEN** a published besluit within the bezwaartermijn
+- **WHEN** a bezwaar is received
+- **THEN** the system MUST support creating a new case linked to the original: type "Bezwaarprocedure"
+- **AND** the original case MUST show: "Bezwaar ontvangen: [datum]"
---
-### REQ-VTH-10: Mobile Inspection Support
+### Requirement: REQ-VTH-10 — Mobile Inspection Support
The system SHALL support mobile inspection workflows for inspectors working in the field.
**Feature tier**: V2
+#### Scenario: Mobile checklist completion
-#### Scenario VTH-10a: Mobile checklist completion
-
-- GIVEN an inspector using a tablet or smartphone in the field
-- WHEN opening a planned inspection from the Nextcloud mobile app or web browser
-- THEN the inspection checklist MUST render in a mobile-friendly layout
-- AND checklist items MUST be completable via large touch targets
-- AND photo capture MUST use the device camera directly
+- **GIVEN** an inspector using a tablet or smartphone in the field
+- **WHEN** opening a planned inspection from the Nextcloud mobile app or web browser
+- **THEN** the inspection checklist MUST render in a mobile-friendly layout
+- **AND** checklist items MUST be completable via large touch targets
+- **AND** photo capture MUST use the device camera directly
-#### Scenario VTH-10b: Offline support
+#### Scenario: Offline support
-- GIVEN an inspector in an area with poor connectivity
-- WHEN the inspector starts filling in a checklist
-- THEN checklist progress MUST be saved locally
-- AND when connectivity returns, the rapport MUST sync to the server
-- AND a visual indicator MUST show sync status (synced/pending/error)
+- **GIVEN** an inspector in an area with poor connectivity
+- **WHEN** the inspector starts filling in a checklist
+- **THEN** checklist progress MUST be saved locally
+- **AND** when connectivity returns, the rapport MUST sync to the server
+- **AND** a visual indicator MUST show sync status (synced/pending/error)
-#### Scenario VTH-10c: GPS location capture
+#### Scenario: GPS location capture
-- GIVEN an inspector completing a checklist
-- WHEN the inspector submits the rapport
-- THEN the device GPS coordinates MUST be automatically captured
-- AND the coordinates MUST be stored on the inspectieRapport
-- AND a map pin MUST be visible in the rapport detail view
+- **GIVEN** an inspector completing a checklist
+- **WHEN** the inspector submits the rapport
+- **THEN** the device GPS coordinates MUST be automatically captured
+- **AND** the coordinates MUST be stored on the inspectieRapport
+- **AND** a map pin MUST be visible in the rapport detail view
---
-### REQ-VTH-11: DSO Intake Mapping
+### Requirement: REQ-VTH-11 — DSO Intake Mapping
The system MUST map DSO verzoek data fields to Procest case properties.
**Feature tier**: V1
+#### Scenario: Field mapping configuration
-#### Scenario VTH-11a: Field mapping configuration
-
-- GIVEN the beheerder configures DSO field mapping
-- WHEN setting up mappings:
+- **GIVEN** the beheerder configures DSO field mapping
+- **WHEN** setting up mappings:
- DSO `aanvraag.bouwkosten` -> case property `bouwkosten`
- DSO `aanvraag.locatie.adres` -> case property `locatie` + linked BAG object
- DSO `aanvraag.aanvrager.bsn` -> participant with role "aanvrager"
- DSO `aanvraag.activiteiten[]` -> case property `activiteiten`
-- THEN the mappings MUST be stored in the admin settings
-- AND new DSO intakes MUST apply these mappings automatically
+- **THEN** the mappings MUST be stored in the admin settings
+- **AND** new DSO intakes MUST apply these mappings automatically
-#### Scenario VTH-11b: Unmapped fields
+#### Scenario: Unmapped fields
-- GIVEN a DSO verzoek with fields not in the mapping configuration
-- WHEN the case is created
-- THEN unmapped fields MUST be stored as raw JSON on the case (custom property "dso_raw")
-- AND a notification MUST be logged: "DSO intake: [n] velden niet gemapped"
+- **GIVEN** a DSO verzoek with fields not in the mapping configuration
+- **WHEN** the case is created
+- **THEN** unmapped fields MUST be stored as raw JSON on the case (custom property "dso_raw")
+- **AND** a notification MUST be logged: "DSO intake: [n] velden niet gemapped"
-#### Scenario VTH-11c: Mapping validation
+#### Scenario: Mapping validation
-- GIVEN a DSO verzoek where mapped fields have unexpected formats
-- WHEN validation fails (e.g., bouwkosten is not a number)
-- THEN the case MUST still be created
-- AND a task MUST flag the validation issue: "DSO intake validatie: bouwkosten formaat ongeldig"
+- **GIVEN** a DSO verzoek where mapped fields have unexpected formats
+- **WHEN** validation fails (e.g., bouwkosten is not a number)
+- **THEN** the case MUST still be created
+- **AND** a task MUST flag the validation issue: "DSO intake validatie: bouwkosten formaat ongeldig"
## Dependencies
diff --git a/openspec/changes/werkvoorraad/specs/werkvoorraad/spec.md b/openspec/changes/werkvoorraad/specs/werkvoorraad/spec.md
deleted file mode 100644
index e1cb3c36..00000000
--- a/openspec/changes/werkvoorraad/specs/werkvoorraad/spec.md
+++ /dev/null
@@ -1,459 +0,0 @@
----
-status: implemented
----
-# Werkvoorraad (Work Queue) Specification
-
-## Purpose
-
-Werkvoorraad extends the existing My Work personal view with team-level work queue management. While My Work (`../my-work/spec.md`) shows a single user's assigned cases and tasks, Werkvoorraad provides team leads and managers with oversight of the full team workload: unassigned cases, workload distribution, bottlenecks, and reassignment capabilities.
-
-**Tender demand**: 23% of tenders (16/69) explicitly require werkvoorraadlijsten and team overview. The capability is also implicit in the 86% that require "gebruikersbeheer en autorisatie" -- managers need to see and manage their team's work.
-**Relationship to existing specs**: This spec EXTENDS `my-work` (personal view) and `task-management` (individual tasks). It does NOT replace them. Werkvoorraad adds the team/management layer on top.
-**Standards**: CMMN 1.1 (work queue patterns), BPMN 2.0 (resource allocation), GEMMA werkvoorraad referentiecomponent
-**Feature tier**: V1 (team overview, unassigned queue, reassignment), V2 (workload balancing, capacity planning, SLA monitoring)
-
-**Competitive context**: Dimpact ZAC implements werkvoorraad as a Solr-backed worklist with separate routes for "mijn zaken" and "werkvoorraad zaken" (unassigned cases in user's groups). ZAC uses Keycloak groups for team scoping and OPA policies for access control. Flowable provides configurable work queues via CMMN case plan models with role-based task assignment. Procest should leverage Nextcloud groups for team scoping, avoiding the need for separate identity infrastructure.
-
-## Requirements
-
----
-
-### REQ-WV-01: Team Work Queue View
-
-The system MUST provide a team work queue showing all open cases and tasks for a team or afdeling, accessible via a dedicated navigation entry.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-01a: Team overview for manager
-
-- GIVEN manager "Teamleider Vergunningen" responsible for team members ["Jan", "Maria", "Pieter", "Anouk"]
-- AND the team has 24 open cases and 45 active tasks distributed across members
-- WHEN the teamleider views the Werkvoorraad
-- THEN the system MUST display:
- - Total open cases (24) and tasks (45) for the team
- - Per-member breakdown: Jan (8 cases, 12 tasks), Maria (6, 11), Pieter (5, 10), Anouk (5, 12)
- - Unassigned cases (0) and tasks (0)
-- AND the teamleider MUST be able to click on a member to see their specific items
-- AND the view MUST load within 3 seconds for teams of up to 20 members
-
-#### Scenario WV-01b: Unassigned work queue
-
-- GIVEN 3 cases and 5 tasks that have no assignee
-- AND the cases belong to case types managed by the team "Vergunningen"
-- WHEN the teamleider views the "Niet-toegewezen" tab
-- THEN all unassigned items MUST be listed
-- AND items MUST be sorted by deadline (nearest first)
-- AND the teamleider MUST be able to assign items to team members from this view via a dropdown
-
-#### Scenario WV-01c: Cross-team manager view
-
-- GIVEN a manager responsible for multiple teams (Vergunningen + Toezicht)
-- WHEN the manager opens the werkvoorraad
-- THEN the system MUST show a team selector allowing switching between teams
-- AND an "Alle teams" option MUST aggregate all teams the manager is responsible for
-- AND team totals MUST be visible in the selector: "Vergunningen (24 zaken)" and "Toezicht (18 zaken)"
-
-#### Scenario WV-01d: No team membership
-
-- GIVEN a user who is not a teamleider and has no werkvoorraad access
-- WHEN the user navigates to the werkvoorraad URL
-- THEN the system MUST show an access denied message: "U heeft geen toegang tot de werkvoorraad. Neem contact op met uw beheerder."
-- AND the navigation item MUST NOT appear for users without werkvoorraad permissions
-
-#### Scenario WV-01e: Empty team queue
-
-- GIVEN a team with no open cases or tasks
-- WHEN the teamleider views the werkvoorraad
-- THEN the system MUST display an empty state: "Geen openstaande zaken of taken voor dit team"
-- AND KPI cards MUST show zeros for all metrics
-
----
-
-### REQ-WV-02: Team Scoping via Nextcloud Groups
-
-The system MUST use Nextcloud groups as the foundation for team scoping. A group is a team when it is configured as such in Procest admin settings.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-02a: Configure team from Nextcloud group
-
-- GIVEN Nextcloud groups "afd-vergunningen" (10 members) and "afd-toezicht" (8 members) exist
-- WHEN the beheerder opens Procest admin settings and configures "afd-vergunningen" as a werkvoorraad team
-- THEN the system MUST store the group ID as a team configuration
-- AND case types MUST be assignable to this team
-- AND members of "afd-vergunningen" MUST appear in the werkvoorraad member list
-
-#### Scenario WV-02b: Teamleider role assignment
-
-- GIVEN group "afd-vergunningen" is configured as a team
-- WHEN the beheerder assigns user "Jan de Vries" as teamleider
-- THEN Jan MUST have access to the werkvoorraad for "afd-vergunningen"
-- AND Jan MUST be able to assign and reassign items within the team
-- AND regular team members MUST NOT see the werkvoorraad (only My Work)
-
-#### Scenario WV-02c: Dynamic group membership
-
-- GIVEN "Maria" is added to Nextcloud group "afd-vergunningen"
-- WHEN the werkvoorraad is next loaded
-- THEN Maria MUST appear in the member list
-- AND any unassigned cases for this team's case types MUST be assignable to Maria
-
----
-
-### REQ-WV-03: Priority and Urgency Sorting
-
-The work queue MUST support sorting by priority, urgency (deadline proximity), and a combined urgency score that weighs both factors.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-03a: Urgency-based sorting
-
-- GIVEN a work queue with items:
- - Case A: priority high, deadline tomorrow
- - Case B: priority normal, deadline overdue by 3 days
- - Case C: priority urgent, deadline in 10 days
- - Case D: priority normal, no deadline
-- WHEN sorted by urgency (default)
-- THEN the order MUST be: B (overdue), A (due tomorrow + high priority), C (urgent), D (no deadline)
-
-#### Scenario WV-03b: Sort by handler workload
-
-- GIVEN 4 team members with varying workloads: Jan (12 items), Maria (6 items), Pieter (9 items), Anouk (3 items)
-- WHEN the teamleider sorts by "Werklast" (workload)
-- THEN members MUST be ordered by total active items descending: Jan (12), Pieter (9), Maria (6), Anouk (3)
-- AND a visual bar chart or indicator MUST show relative workload
-
-#### Scenario WV-03c: Sort by days in current status
-
-- GIVEN 3 cases in status "In behandeling" for different durations
- - Case X: 2 days in current status
- - Case Y: 15 days in current status
- - Case Z: 8 days in current status
-- WHEN the teamleider sorts by "Dagen in huidige status"
-- THEN the order MUST be: Y (15), Z (8), X (2)
-- AND cases exceeding the average processing time for their type MUST be highlighted
-
----
-
-### REQ-WV-04: Filters and Views
-
-The work queue MUST support filtering by zaaktype, status, afdeling, handler, deadline range, and priority.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-04a: Filter by zaaktype
-
-- GIVEN a team handling cases of types "Omgevingsvergunning" (10), "Sloopmelding" (8), "Milieumelding" (6)
-- WHEN the teamleider filters by "Omgevingsvergunning"
-- THEN only the 10 omgevingsvergunning cases MUST be shown
-- AND the filter MUST show counts per zaaktype for quick selection
-
-#### Scenario WV-04b: Filter overdue items
-
-- GIVEN 5 cases past their deadline
-- WHEN the teamleider selects filter "Verlopen termijn"
-- THEN only the 5 overdue cases MUST be shown
-- AND each case MUST show the number of days overdue with severity coloring: 1-3 days (orange), 4+ days (red)
-
-#### Scenario WV-04c: Filter by handler
-
-- GIVEN the teamleider wants to see all work for "Pieter"
-- WHEN selecting handler filter "Pieter"
-- THEN only Pieter's cases and tasks MUST be shown
-- AND the view MUST include both active and recently completed items (last 7 days)
-
-#### Scenario WV-04d: Combine multiple filters
-
-- GIVEN filters: zaaktype = "Omgevingsvergunning", status = "In behandeling", handler = unassigned
-- WHEN all three filters are active simultaneously
-- THEN only unassigned omgevingsvergunning cases in "In behandeling" status MUST be shown
-- AND the active filters MUST be displayed as removable chips above the list
-
-#### Scenario WV-04e: Save filter preset
-
-- GIVEN the teamleider has configured a useful filter combination
-- WHEN the teamleider clicks "Opslaan als weergave" and names it "Urgente vergunningen"
-- THEN the preset MUST be stored per user
-- AND the preset MUST appear in a quick-access dropdown for future use
-
----
-
-### REQ-WV-05: Bulk Reassignment
-
-The system MUST support reassigning multiple cases or tasks at once, for example when a team member is absent.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-05a: Reassign all work from absent colleague
-
-- GIVEN Maria is on sick leave with 6 open cases and 11 tasks
-- WHEN the teamleider selects all of Maria's items and clicks "Herverdelen"
-- THEN the system MUST allow selecting a target assignee (or "round-robin" across team)
-- AND all selected items MUST be reassigned
-- AND each reassignment MUST be recorded in the audit trail: "Herverdeeld van Maria naar [target] door [teamleider], reden: afwezigheid"
-
-#### Scenario WV-05b: Round-robin distribution
-
-- GIVEN 8 unassigned cases to distribute across 4 team members [Jan, Maria, Pieter, Anouk]
-- AND current workloads are: Jan (5), Maria (3), Pieter (4), Anouk (2)
-- WHEN the teamleider selects "Gelijkmatig verdelen" (balanced distribution)
-- THEN the system MUST distribute cases to balance workloads: Anouk gets 3, Maria gets 2, Pieter gets 2, Jan gets 1
-- AND each assignment MUST be recorded in the audit trail
-
-#### Scenario WV-05c: Partial reassignment
-
-- GIVEN the teamleider selects 3 of Maria's 6 cases using checkboxes
-- WHEN clicking "Toewijzen aan" and selecting "Pieter"
-- THEN only the 3 selected cases MUST be reassigned to Pieter
-- AND Maria MUST retain the other 3 cases
-
-#### Scenario WV-05d: Reassignment with reason
-
-- GIVEN the teamleider reassigns cases from Maria to Pieter
-- WHEN the reassignment dialog opens
-- THEN a reason field MUST be presented (optional but recommended)
-- AND common reasons MUST be available as quick-select: "Afwezigheid", "Capaciteit", "Expertise", "Anders"
-- AND the reason MUST be stored in the audit trail
-
----
-
-### REQ-WV-06: Deadline Monitoring and Escalation
-
-The system MUST actively monitor deadlines and alert when cases approach or pass their deadline.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-06a: Deadline warning notification
-
-- GIVEN a case with deadline in 3 days and no status change in the last 5 days
-- WHEN the daily deadline check runs (Nextcloud background job)
-- THEN the handler AND the teamleider MUST receive a Nextcloud notification: "Zaak [identifier] nadert deadline (nog 3 dagen)"
-- AND the notification MUST link to the case detail page
-
-#### Scenario WV-06b: Overdue escalation
-
-- GIVEN a case 2 days past its deadline
-- WHEN the daily deadline check runs
-- THEN the teamleider MUST receive an escalation notification: "Zaak [identifier] is 2 dagen over de termijn"
-- AND the case MUST appear in the "Verlopen" section of the werkvoorraad with a red indicator
-- AND the notification MUST NOT be sent again for the same case on subsequent days (only on day 1 overdue)
-
-#### Scenario WV-06c: Configurable warning thresholds
-
-- GIVEN the beheerder configures warning thresholds per case type:
- - Omgevingsvergunning: warn at 5 days before deadline
- - Sloopmelding: warn at 3 days before deadline
-- WHEN a case approaches the configured threshold
-- THEN the notification MUST fire at the configured interval, not a global default
-
-#### Scenario WV-06d: SLA indicator on queue items
-
-- GIVEN a case type "Omgevingsvergunning" with processing deadline of 8 weeks
-- AND a case has been open for 6 weeks (75% of deadline consumed)
-- WHEN the case appears in the werkvoorraad
-- THEN it MUST display a progress indicator showing 75% time consumed
-- AND the indicator MUST use color coding: green (0-60%), orange (60-85%), red (85-100%), dark red (>100%)
-
-#### Scenario WV-06e: Weekly deadline summary
-
-- GIVEN a team with 3 cases due this week and 2 cases overdue
-- WHEN Monday morning arrives (configurable day)
-- THEN the teamleider MUST receive a summary notification: "Werkvoorraad weekoverzicht: 3 zaken deze week, 2 verlopen"
-- AND the summary MUST be optional (configurable in user settings)
-
----
-
-### REQ-WV-07: Werkvoorraad Dashboard KPIs
-
-The werkvoorraad MUST display key performance indicators at the top of the view, providing at-a-glance team health metrics.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-07a: Team KPI cards
-
-- GIVEN a team with 24 open cases, 3 overdue, 12 completed this week, average processing time 14 days
-- WHEN the teamleider views the werkvoorraad
-- THEN KPI cards MUST display: "Open zaken" (24), "Verlopen" (3, in red), "Afgehandeld deze week" (12), "Gem. doorlooptijd" (14 dagen)
-- AND each card MUST be clickable to filter the queue to that subset
-
-#### Scenario WV-07b: Trend indicators
-
-- GIVEN last week the team had 28 open cases and this week 24
-- WHEN the KPI cards render
-- THEN the "Open zaken" card MUST show a trend arrow: down arrow with "-4" indicating improvement
-- AND the "Verlopen" card MUST show trend compared to last week
-
-#### Scenario WV-07c: Case type distribution
-
-- GIVEN the team handles 3 case types with varying volumes
-- WHEN the teamleider views the werkvoorraad dashboard
-- THEN a distribution chart MUST show: "Omgevingsvergunning" (10), "Sloopmelding" (8), "Milieumelding" (6)
-- AND the chart MUST use the existing `StatusChart.vue` component pattern
-
----
-
-### REQ-WV-08: Workload Statistics
-
-The system SHALL provide workload statistics for capacity planning and performance monitoring.
-
-**Feature tier**: V2
-
-
-#### Scenario WV-08a: Team capacity overview
-
-- GIVEN a team of 4 members with varying workloads
-- WHEN the teamleider views the capacity overview
-- THEN the system MUST show per member: open cases count, active tasks count, average processing time, cases completed this week
-- AND members with significantly above-average workload (>150% of mean) MUST be highlighted in orange
-
-#### Scenario WV-08b: Historical throughput
-
-- GIVEN the team has been active for 3 months
-- WHEN the teamleider views the "Doorlooptijd" (throughput) tab
-- THEN the system MUST show a chart with: cases opened per week, cases closed per week, average backlog size
-- AND the chart MUST allow selecting different time ranges: 1 week, 1 month, 3 months, 6 months
-
-#### Scenario WV-08c: Per-case-type processing time
-
-- GIVEN case type "Omgevingsvergunning" has a legal deadline of 8 weeks
-- AND the team's average processing time for this type is 5.2 weeks
-- WHEN the teamleider views case type statistics
-- THEN the system MUST show: average processing time (5.2 weeks), legal deadline (8 weeks), percentage within deadline (92%), number completed (48)
-
----
-
-### REQ-WV-09: Export and Reporting
-
-The werkvoorraad MUST support exporting filtered queue data for external reporting and management meetings.
-
-**Feature tier**: V2
-
-
-#### Scenario WV-09a: CSV export of current view
-
-- GIVEN the teamleider has filtered the werkvoorraad to show overdue cases
-- WHEN clicking the "Exporteren" button
-- THEN the system MUST generate a CSV file with columns: zaakidentificatie, titel, zaaktype, status, behandelaar, startdatum, deadline, dagen verlopen
-- AND the export MUST respect the current filter and sort order
-
-#### Scenario WV-09b: PDF summary report
-
-- GIVEN the teamleider wants a weekly report for management
-- WHEN clicking "Rapport genereren"
-- THEN the system MUST generate a PDF containing: KPI summary, overdue cases list, workload distribution per member, case type distribution chart
-- AND the report MUST include the date range and team name
-
----
-
-### REQ-WV-10: Real-Time Updates
-
-The werkvoorraad SHALL update in near-real-time when cases are created, assigned, or status-changed by other users.
-
-**Feature tier**: V2
-
-
-#### Scenario WV-10a: Case assigned by another user
-
-- GIVEN the teamleider has the werkvoorraad open showing 3 unassigned cases
-- AND another behandelaar assigns one of those cases to themselves via the case detail page
-- WHEN the werkvoorraad refreshes (polling every 30 seconds or WebSocket)
-- THEN the unassigned count MUST update from 3 to 2
-- AND the assigned case MUST move to the correct team member's column
-
-#### Scenario WV-10b: New case created
-
-- GIVEN a new case is created via DSO intake that matches the team's case types
-- WHEN the werkvoorraad refreshes
-- THEN the new case MUST appear in the unassigned queue
-- AND the "Open zaken" KPI MUST increment
-
----
-
-### REQ-WV-11: Accessibility
-
-The werkvoorraad MUST meet WCAG AA accessibility requirements.
-
-**Feature tier**: V1
-
-
-#### Scenario WV-11a: Screen reader navigation
-
-- GIVEN a screen reader user navigating the werkvoorraad
-- THEN all interactive elements (tabs, filters, assignment dropdowns, checkboxes) MUST have appropriate ARIA labels
-- AND the KPI cards MUST announce their values: "Open zaken: 24"
-- AND table rows MUST be navigable via keyboard (Tab/Arrow keys)
-
-#### Scenario WV-11b: Keyboard bulk selection
-
-- GIVEN a keyboard-only user viewing the werkvoorraad
-- WHEN pressing Space on a row to select it, then Shift+Space to extend selection
-- THEN the selection MUST work identically to mouse-based checkbox selection
-- AND the "Herverdelen" action MUST be triggerable via keyboard
-
-## Dependencies
-
-- **My Work spec** (`../my-work/spec.md`): Personal view; Werkvoorraad adds the team layer.
-- **Task Management spec** (`../task-management/spec.md`): Tasks are the atomic work items.
-- **Case Management spec** (`../case-management/spec.md`): Cases are the primary work units.
-- **Admin Settings spec** (`../admin-settings/spec.md`): Team/afdeling configuration, warning thresholds.
-- **Dashboard spec** (`../dashboard/spec.md`): Shared KPI card and chart components.
-- **OpenRegister**: All queries against `procest` register, filtered by assignee and team membership.
-- **Nextcloud Groups**: Team scoping uses Nextcloud's `IGroupManager` for group membership queries.
-
----
-
-### Current Implementation Status
-
-**Not implemented as a team-level feature.** The personal My Work view exists (`src/views/MyWork.vue`) but no team/manager werkvoorraad functionality has been built.
-
-**Existing foundations that relate to this spec:**
-- **My Work view**: `src/views/MyWork.vue` -- personal workload view showing cases and tasks assigned to the current user. Includes grouping by urgency (overdue, due this week, upcoming, no deadline), filter tabs (all/cases/tasks), overdue highlighting, and show-completed toggle. This is the base UI pattern that werkvoorraad extends with team oversight.
-- **Dashboard widgets**: `lib/Dashboard/CasesOverviewWidget.php` and `src/views/widgets/CasesOverviewWidget.vue` -- shows case overview, could be extended for team statistics. `lib/Dashboard/OverdueCasesWidget.php` shows overdue cases with severity-based coloring.
-- **Dashboard panels**: `src/views/dashboard/KpiCards.vue` shows KPI summary cards (open cases, new today, overdue, completed this month, avg processing days, my tasks, tasks due today). `src/views/dashboard/StatusChart.vue` shows status distribution. `src/views/dashboard/OverduePanel.vue` shows overdue items with days-overdue count.
-- **Object store**: The `useObjectStore()` can filter by `assignee` and `status`, which could be used for team member workload queries.
-- **Case assignee field**: Cases have an `assignee` field (Nextcloud user UID) in the `case` schema, enabling per-user workload queries.
-- **Task assignee field**: Tasks have an `assignee` field, enabling per-user task queries.
-- **Notification service**: `lib/Service/NotificatieService.php` provides notification infrastructure for deadline alerts.
-
-**Not yet implemented:**
-- **REQ-WV-01: Team work queue view**: No team overview, no per-member breakdown, no unassigned items queue.
-- **REQ-WV-02: Team scoping**: No concept of teams or departments (afdelingen) in the data model. Nextcloud groups exist but are not used for team scoping.
-- **REQ-WV-03: Priority/urgency sorting**: The My Work view has urgency grouping, but no combined urgency score or team-level sorting.
-- **REQ-WV-04: Filters and views**: No zaaktype filter, afdeling filter, or deadline range filter on a team-level view.
-- **REQ-WV-05: Bulk reassignment**: No multi-select or bulk reassignment capability. Only individual handler reassignment exists in `ParticipantsSection.vue`.
-- **REQ-WV-06: Deadline monitoring**: No automated daily deadline checks or notification triggers. Overdue display is purely visual (client-side calculation in `OverduePanel.vue`).
-- **REQ-WV-07: Werkvoorraad KPIs**: Dashboard KPIs exist but are personal-scoped, not team-scoped.
-- **REQ-WV-08: Workload statistics (V2)**: No capacity overview, average processing time, or workload comparison.
-- **REQ-WV-09: Export (V2)**: No CSV or PDF export functionality.
-- **REQ-WV-10: Real-time updates (V2)**: No polling or WebSocket for live updates.
-- **REQ-WV-11: Accessibility**: Existing My Work view needs WCAG audit.
-
-### Standards & References
-
-- **CMMN 1.1**: Work queue patterns for distributing and managing case work items.
-- **BPMN 2.0**: Resource allocation patterns for work distribution.
-- **ZGW APIs**: The ZGW model does not define a werkvoorraad concept directly, but team-based case filtering is standard in Dutch zaaksystemen. Dimpact ZAC implements separate `/zaken/werkvoorraad` and `/zaken/mijn` worklist routes.
-- **GEMMA**: Werkvoorraad is a standard component in the GEMMA reference architecture for zaakgericht werken.
-- **Common Ground**: The werkvoorraad is typically a process-layer concern built on top of the ZGW information layer.
-- **BIO**: Audit trail requirements for reassignment actions (who reassigned, when, why).
-- **WCAG 2.1 AA**: Accessibility requirements for government applications.
-- **Nextcloud OCP**: `IGroupManager` for group membership queries, `INotificationManager` for deadline notifications.
-
-### Specificity Assessment
-
-- **V1 requirements are well-specified** with concrete scenarios for team overview, team scoping, filtering, bulk reassignment, deadline monitoring, and KPIs.
-- **Team scoping is now defined**: Teams are Nextcloud groups configured as werkvoorraad teams in admin settings. Teamleiders are designated users with werkvoorraad access.
-- **Resolved open questions:**
- - Teams are defined via Nextcloud groups (REQ-WV-02).
- - Access is controlled by teamleider role assignment (REQ-WV-02b).
- - Werkvoorraad is a separate navigation item, not a tab in My Work (REQ-WV-01).
- - Notifications use Nextcloud background jobs (REQ-WV-06a).
- - Team membership is determined by Nextcloud group membership (REQ-WV-02c).
- - Round-robin distribution uses workload-aware balancing (REQ-WV-05b).
diff --git a/openspec/changes/woo-case-type/specs/woo-case-type/spec.md b/openspec/changes/woo-case-type/specs/woo-case-type/spec.md
index 4457f2d1..08bac589 100644
--- a/openspec/changes/woo-case-type/specs/woo-case-type/spec.md
+++ b/openspec/changes/woo-case-type/specs/woo-case-type/spec.md
@@ -9,48 +9,47 @@ Provide a standard zaaktype template in Procest for handling WOO (Wet open overh
## Context
The Wet open overheid (WOO) replaced the Wet openbaarheid van bestuur (WOB) in 2022. Municipalities must handle transparency requests within strict deadlines. A WOO request follows a specific lifecycle that differs from regular cases: it requires searching across document systems, per-document assessment (openbaar/deels openbaar/geweigerd), redaction of privacy-sensitive information, and publication to a reading room. This zaaktype template provides the standard workflow out of the box.
-## Requirements
-
-### Requirement 1: WOO zaaktype template activation
+## ADDED Requirements
+### Requirement: WOO zaaktype template activation
The system MUST provide a pre-configured zaaktype template for WOO verzoeken that can be activated from a template library and customized per municipality.
-#### Scenario 1.1: Activate WOO zaaktype from template library
-- GIVEN a Procest admin navigating to the case type settings at `CaseTypeAdmin.vue`
-- WHEN they open the template library and select "WOO Verzoek"
-- THEN the system MUST create a new zaaktype object in OpenRegister using the `caseType` schema
-- AND populate it with pre-configured status types, property definitions, document types, role types, result types, and decision types
-- AND redirect the admin to `CaseTypeDetail.vue` for the new zaaktype
-
-#### Scenario 1.2: Customize template before activation
-- GIVEN an admin previewing the "WOO Verzoek" template
-- WHEN they modify the template configuration (e.g., change a stage name, add a property)
-- THEN the customizations MUST be applied to the created zaaktype
-- AND the original template MUST remain unchanged for future activations
-
-#### Scenario 1.3: Template includes version metadata
-- GIVEN the WOO zaaktype template
-- THEN it MUST include a version number (e.g., "1.0.0") and a last-updated date
-- AND when Procest ships a template update, admins MUST be notified that a newer version is available
-- AND updating MUST NOT overwrite customizations already applied to existing zaaktypes
-
-#### Scenario 1.4: Template ships as JSON seed data
-- GIVEN the Procest app installation
-- THEN the WOO template MUST be stored as a JSON file in `lib/Settings/templates/woo-verzoek.json`
-- AND the `InitializeSettings` repair step MUST register the template in the template registry
-- AND the template format MUST follow the same structure as `procest_register.json`
-
-#### Scenario 1.5: Multiple templates can coexist
-- GIVEN the template library contains "WOO Verzoek" and other zaaktype templates
-- WHEN an admin activates "WOO Verzoek"
-- THEN it MUST NOT interfere with other activated templates or manually created zaaktypes
-- AND each activated template MUST create its own independent set of status types and property definitions
-
-### Requirement 2: WOO lifecycle stages
+#### Scenario: Activate WOO zaaktype from template library
+- **GIVEN** a Procest admin navigating to the case type settings at `CaseTypeAdmin.vue`
+- **WHEN** they open the template library and select "WOO Verzoek"
+- **THEN** the system MUST create a new zaaktype object in OpenRegister using the `caseType` schema
+- **AND** populate it with pre-configured status types, property definitions, document types, role types, result types, and decision types
+- **AND** redirect the admin to `CaseTypeDetail.vue` for the new zaaktype
+
+#### Scenario: Customize template before activation
+- **GIVEN** an admin previewing the "WOO Verzoek" template
+- **WHEN** they modify the template configuration (e.g., change a stage name, add a property)
+- **THEN** the customizations MUST be applied to the created zaaktype
+- **AND** the original template MUST remain unchanged for future activations
+
+#### Scenario: Template includes version metadata
+- **GIVEN** the WOO zaaktype template
+- **THEN** it MUST include a version number (e.g., "1.0.0") and a last-updated date
+- **AND** when Procest ships a template update, admins MUST be notified that a newer version is available
+- **AND** updating MUST NOT overwrite customizations already applied to existing zaaktypes
+
+#### Scenario: Template ships as JSON seed data
+- **GIVEN** the Procest app installation
+- **THEN** the WOO template MUST be stored as a JSON file in `lib/Settings/templates/woo-verzoek.json`
+- **AND** the `InitializeSettings` repair step MUST register the template in the template registry
+- **AND** the template format MUST follow the same structure as `procest_register.json`
+
+#### Scenario: Multiple templates can coexist
+- **GIVEN** the template library contains "WOO Verzoek" and other zaaktype templates
+- **WHEN** an admin activates "WOO Verzoek"
+- **THEN** it MUST NOT interfere with other activated templates or manually created zaaktypes
+- **AND** each activated template MUST create its own independent set of status types and property definitions
+
+### Requirement: WOO lifecycle stages
The WOO zaaktype MUST define eight ordered stages reflecting the statutory WOO processing flow.
-#### Scenario 2.1: Pre-configured stage order
-- GIVEN the activated WOO Verzoek zaaktype
-- THEN the following status types MUST be created in the `statusType` schema in order:
+#### Scenario: Pre-configured stage order
+- **GIVEN** the activated WOO Verzoek zaaktype
+- **THEN** the following status types MUST be created in the `statusType` schema in order:
1. **Ontvangst** -- Request received, acknowledgement sent
2. **Beoordeling ontvankelijkheid** -- Check formal requirements
3. **Zoeken documenten** -- Search and collect relevant documents
@@ -59,39 +58,39 @@ The WOO zaaktype MUST define eight ordered stages reflecting the statutory WOO p
6. **Besluit** -- Formal decision on disclosure
7. **Publicatie** -- Publish approved documents
8. **Afgehandeld** -- Case closed (isFinal: true)
-- AND the `StatusesTab.vue` MUST render these as a visual flow diagram
-
-#### Scenario 2.2: Stage transitions enforce order
-- GIVEN a WOO case in stage "Ontvangst"
-- WHEN the case worker attempts to change the status
-- THEN only "Beoordeling ontvankelijkheid" MUST be available as the next status
-- AND skipping stages (e.g., going from "Ontvangst" directly to "Besluit") MUST be blocked unless the admin has configured skip-ahead for specific stages
-
-#### Scenario 2.3: Stage-specific required actions
-- GIVEN a WOO case in stage "Beoordelen documenten"
-- WHEN the case worker attempts to advance to "Lakken / Anonimiseren"
-- THEN the system MUST verify that all collected documents have been assessed (openbaar/deels openbaar/niet openbaar)
-- AND block advancement if any document lacks an assessment
-
-#### Scenario 2.4: Return to previous stage
-- GIVEN a WOO case in stage "Besluit"
-- WHEN the case worker determines that additional documents need assessment
-- THEN they MUST be able to return the case to "Beoordelen documenten" with a mandatory reason
-- AND the return MUST be logged in the audit trail via the `ActivityTimeline` component
-
-#### Scenario 2.5: Initiator notification per stage
-- GIVEN a WOO stage with `notifyInitiator: true` configured on the status type
-- WHEN the case transitions to that stage
-- THEN the system MUST send a notification to the verzoeker using the `notificationText` from the status type
-- AND the notification MUST be recorded in the case timeline
-
-### Requirement 3: WOO-specific intake form
+- **AND** the `StatusesTab.vue` MUST render these as a visual flow diagram
+
+#### Scenario: Stage transitions enforce order
+- **GIVEN** a WOO case in stage "Ontvangst"
+- **WHEN** the case worker attempts to change the status
+- **THEN** only "Beoordeling ontvankelijkheid" MUST be available as the next status
+- **AND** skipping stages (e.g., going from "Ontvangst" directly to "Besluit") MUST be blocked unless the admin has configured skip-ahead for specific stages
+
+#### Scenario: Stage-specific required actions
+- **GIVEN** a WOO case in stage "Beoordelen documenten"
+- **WHEN** the case worker attempts to advance to "Lakken / Anonimiseren"
+- **THEN** the system MUST verify that all collected documents have been assessed (openbaar/deels openbaar/niet openbaar)
+- **AND** block advancement if any document lacks an assessment
+
+#### Scenario: Return to previous stage
+- **GIVEN** a WOO case in stage "Besluit"
+- **WHEN** the case worker determines that additional documents need assessment
+- **THEN** they MUST be able to return the case to "Beoordelen documenten" with a mandatory reason
+- **AND** the return MUST be logged in the audit trail via the `ActivityTimeline` component
+
+#### Scenario: Initiator notification per stage
+- **GIVEN** a WOO stage with `notifyInitiator: true` configured on the status type
+- **WHEN** the case transitions to that stage
+- **THEN** the system MUST send a notification to the verzoeker using the `notificationText` from the status type
+- **AND** the notification MUST be recorded in the case timeline
+
+### Requirement: WOO-specific intake form
The system MUST provide intake fields specific to WOO requests as property definitions on the zaaktype.
-#### Scenario 3.1: Required intake fields
-- GIVEN a new WOO Verzoek case being created via `CaseCreateDialog.vue`
-- WHEN the case worker fills in the intake form
-- THEN the form MUST include these property definitions:
+#### Scenario: Required intake fields
+- **GIVEN** a new WOO Verzoek case being created via `CaseCreateDialog.vue`
+- **WHEN** the case worker fills in the intake form
+- **THEN** the form MUST include these property definitions:
- **Verzoeker naam** (string, required)
- **Verzoeker contactgegevens** (email/phone, required)
- **Verzoeker type** (enum: burger/journalist/organisatie, required)
@@ -101,112 +100,112 @@ The system MUST provide intake fields specific to WOO requests as property defin
- **Ontvangstdatum** (date, required -- receipt date for deadline calculation)
- **Gewenste vorm** (enum: papier/digitaal/inzage, default: digitaal)
-#### Scenario 3.2: Intake form validation
-- GIVEN a case worker submitting the WOO intake form
-- WHEN required fields are missing
-- THEN the system MUST highlight missing fields with validation errors
-- AND block case creation until all required fields are filled
-
-#### Scenario 3.3: Verzoeker linked to contact record
-- GIVEN a WOO intake with verzoeker details
-- WHEN the case is created
-- THEN the verzoeker MUST be linked as a role (using the `role` schema with roleType "Verzoeker")
-- AND if the verzoeker matches an existing contact in OpenRegister, the system MUST suggest linking to the existing record
-
-#### Scenario 3.4: Intake auto-generates ontvangstbevestiging task
-- GIVEN a WOO case is created with ontvangstdatum filled
-- WHEN the case enters the "Ontvangst" stage
-- THEN a task MUST be auto-created: "Verstuur ontvangstbevestiging" with a 2-day deadline
-- AND the task MUST reference the verzoeker's contact details
-
-#### Scenario 3.5: Intake from external channel
-- GIVEN a WOO request arrives via email, post, or a web form through OpenConnector
-- WHEN the request is routed to Procest
-- THEN the system MUST pre-fill the intake form with available data from the incoming message
-- AND flag any missing required fields for manual completion
-
-### Requirement 4: WOO deadline tracking and extension
+#### Scenario: Intake form validation
+- **GIVEN** a case worker submitting the WOO intake form
+- **WHEN** required fields are missing
+- **THEN** the system MUST highlight missing fields with validation errors
+- **AND** block case creation until all required fields are filled
+
+#### Scenario: Verzoeker linked to contact record
+- **GIVEN** a WOO intake with verzoeker details
+- **WHEN** the case is created
+- **THEN** the verzoeker MUST be linked as a role (using the `role` schema with roleType "Verzoeker")
+- **AND** if the verzoeker matches an existing contact in OpenRegister, the system MUST suggest linking to the existing record
+
+#### Scenario: Intake auto-generates ontvangstbevestiging task
+- **GIVEN** a WOO case is created with ontvangstdatum filled
+- **WHEN** the case enters the "Ontvangst" stage
+- **THEN** a task MUST be auto-created: "Verstuur ontvangstbevestiging" with a 2-day deadline
+- **AND** the task MUST reference the verzoeker's contact details
+
+#### Scenario: Intake from external channel
+- **GIVEN** a WOO request arrives via email, post, or a web form through OpenConnector
+- **WHEN** the request is routed to Procest
+- **THEN** the system MUST pre-fill the intake form with available data from the incoming message
+- **AND** flag any missing required fields for manual completion
+
+### Requirement: WOO deadline tracking and extension
The system MUST enforce WOO-mandated response deadlines with support for statutory extension.
-#### Scenario 4.1: Calculate initial response deadline
-- GIVEN a WOO request with ontvangstdatum "2026-03-15"
-- WHEN the case is created
-- THEN the system MUST set the case deadline to 28 calendar days: "2026-04-12"
-- AND display the deadline in the `DeadlinePanel.vue` component
-- AND store the deadline as an ISO 8601 duration (P28D) on the case type's processing time
-
-#### Scenario 4.2: Warning thresholds
-- GIVEN a WOO case with a deadline of "2026-04-12"
-- WHEN 14 days remain (2026-03-29), the `DeadlinePanel` MUST show a yellow warning
-- AND when 7 days remain (2026-04-05), the panel MUST show a red urgent alert
-- AND when the deadline has passed, the panel MUST show "Termijn verlopen" in red
-
-#### Scenario 4.3: Request deadline extension (verdaging)
-- GIVEN a WOO case approaching its deadline with extensionCount === 0
-- WHEN the case worker clicks "Request Extension" in the `DeadlinePanel`
-- THEN a dialog MUST appear requesting a mandatory reason for the extension
-- AND the deadline MUST be extended by 14 calendar days (P14D)
-- AND the extension MUST be logged in the audit trail
-- AND the verzoeker MUST be notified of the extension with the reason
-
-#### Scenario 4.4: Only one extension allowed
-- GIVEN a WOO case that has already been extended once (extensionCount === 1)
-- THEN the "Request Extension" button MUST be disabled
-- AND the `DeadlinePanel` MUST show "Verdaging: reeds verdaagd"
-
-#### Scenario 4.5: Extension resets warning thresholds
-- GIVEN a WOO case with original deadline "2026-04-12" extended to "2026-04-26"
-- THEN the warning threshold MUST recalculate to 14 days before "2026-04-26"
-- AND the urgent alert threshold MUST recalculate to 7 days before "2026-04-26"
-
-### Requirement 5: Document collection and inventory
+#### Scenario: Calculate initial response deadline
+- **GIVEN** a WOO request with ontvangstdatum "2026-03-15"
+- **WHEN** the case is created
+- **THEN** the system MUST set the case deadline to 28 calendar days: "2026-04-12"
+- **AND** display the deadline in the `DeadlinePanel.vue` component
+- **AND** store the deadline as an ISO 8601 duration (P28D) on the case type's processing time
+
+#### Scenario: Warning thresholds
+- **GIVEN** a WOO case with a deadline of "2026-04-12"
+- **WHEN** 14 days remain (2026-03-29), the `DeadlinePanel` MUST show a yellow warning
+- **AND** when 7 days remain (2026-04-05), the panel MUST show a red urgent alert
+- **AND** when the deadline has passed, the panel MUST show "Termijn verlopen" in red
+
+#### Scenario: Request deadline extension (verdaging)
+- **GIVEN** a WOO case approaching its deadline with extensionCount === 0
+- **WHEN** the case worker clicks "Request Extension" in the `DeadlinePanel`
+- **THEN** a dialog MUST appear requesting a mandatory reason for the extension
+- **AND** the deadline MUST be extended by 14 calendar days (P14D)
+- **AND** the extension MUST be logged in the audit trail
+- **AND** the verzoeker MUST be notified of the extension with the reason
+
+#### Scenario: Only one extension allowed
+- **GIVEN** a WOO case that has already been extended once (extensionCount === 1)
+- **THEN** the "Request Extension" button MUST be disabled
+- **AND** the `DeadlinePanel` MUST show "Verdaging: reeds verdaagd"
+
+#### Scenario: Extension resets warning thresholds
+- **GIVEN** a WOO case with original deadline "2026-04-12" extended to "2026-04-26"
+- **THEN** the warning threshold MUST recalculate to 14 days before "2026-04-26"
+- **AND** the urgent alert threshold MUST recalculate to 7 days before "2026-04-26"
+
+### Requirement: Document collection and inventory
The system MUST support searching, collecting, and inventorying documents relevant to a WOO request.
-#### Scenario 5.1: Add documents to WOO dossier
-- GIVEN a WOO case in stage "Zoeken documenten"
-- WHEN the case worker searches Nextcloud files or external document systems
-- THEN found documents MUST be linkable to the WOO case as case documents (using the `caseDocument` schema)
-- AND each document MUST receive a sequential inventory number
-
-#### Scenario 5.2: Document inventory list (inventarislijst)
-- GIVEN a WOO case with 25 collected documents
-- WHEN the case worker views the document inventory
-- THEN the system MUST display a table with columns: volgnummer, documentnaam, datum, afzender, beoordeling, weigeringsgrond
-- AND the inventory MUST be exportable as a PDF/CSV for inclusion in the WOO decision
-
-#### Scenario 5.3: Bulk document upload
-- GIVEN a WOO case in stage "Zoeken documenten"
-- WHEN the case worker uploads multiple documents at once via drag-and-drop
-- THEN all documents MUST be linked to the case with sequential inventory numbers
-- AND duplicate detection MUST warn if a document with the same filename already exists in the dossier
-
-#### Scenario 5.4: Document source tracking
-- GIVEN a document added to a WOO dossier
-- THEN the system MUST record the source system (Nextcloud Files, email, external DMS) and the search query or context that led to the document
-- AND this metadata MUST be included in the inventarislijst
-
-#### Scenario 5.5: Mark document collection complete
-- GIVEN a WOO case in stage "Zoeken documenten"
-- WHEN the case worker marks document collection as complete
-- THEN the system MUST verify at least one document has been collected
-- AND advance the case to "Beoordelen documenten"
-
-### Requirement 6: Per-document disclosure assessment
+#### Scenario: Add documents to WOO dossier
+- **GIVEN** a WOO case in stage "Zoeken documenten"
+- **WHEN** the case worker searches Nextcloud files or external document systems
+- **THEN** found documents MUST be linkable to the WOO case as case documents (using the `caseDocument` schema)
+- **AND** each document MUST receive a sequential inventory number
+
+#### Scenario: Document inventory list (inventarislijst)
+- **GIVEN** a WOO case with 25 collected documents
+- **WHEN** the case worker views the document inventory
+- **THEN** the system MUST display a table with columns: volgnummer, documentnaam, datum, afzender, beoordeling, weigeringsgrond
+- **AND** the inventory MUST be exportable as a PDF/CSV for inclusion in the WOO decision
+
+#### Scenario: Bulk document upload
+- **GIVEN** a WOO case in stage "Zoeken documenten"
+- **WHEN** the case worker uploads multiple documents at once via drag-and-drop
+- **THEN** all documents MUST be linked to the case with sequential inventory numbers
+- **AND** duplicate detection MUST warn if a document with the same filename already exists in the dossier
+
+#### Scenario: Document source tracking
+- **GIVEN** a document added to a WOO dossier
+- **THEN** the system MUST record the source system (Nextcloud Files, email, external DMS) and the search query or context that led to the document
+- **AND** this metadata MUST be included in the inventarislijst
+
+#### Scenario: Mark document collection complete
+- **GIVEN** a WOO case in stage "Zoeken documenten"
+- **WHEN** the case worker marks document collection as complete
+- **THEN** the system MUST verify at least one document has been collected
+- **AND** advance the case to "Beoordelen documenten"
+
+### Requirement: Per-document disclosure assessment
Each document in a WOO case MUST be individually assessed for disclosure with mandatory legal basis for withheld documents.
-#### Scenario 6.1: Three-way assessment per document
-- GIVEN a WOO case in stage "Beoordelen documenten" with 15 collected documents
-- WHEN the case worker opens the document assessment view
-- THEN each document MUST have a dropdown with options:
+#### Scenario: Three-way assessment per document
+- **GIVEN** a WOO case in stage "Beoordelen documenten" with 15 collected documents
+- **WHEN** the case worker opens the document assessment view
+- **THEN** each document MUST have a dropdown with options:
- **Openbaar** -- Full disclosure
- **Deels openbaar** -- Partial disclosure (requires redaction)
- **Niet openbaar** -- Withheld (requires weigeringsgrond)
-- AND unassessed documents MUST be visually distinct (grey/pending state)
+- **AND** unassessed documents MUST be visually distinct (grey/pending state)
-#### Scenario 6.2: Mandatory weigeringsgrond for withheld documents
-- GIVEN a document assessed as "Niet openbaar"
-- WHEN the case worker saves the assessment
-- THEN they MUST select one or more legal grounds from WOO Article 5.1/5.2:
+#### Scenario: Mandatory weigeringsgrond for withheld documents
+- **GIVEN** a document assessed as "Niet openbaar"
+- **WHEN** the case worker saves the assessment
+- **THEN** they MUST select one or more legal grounds from WOO Article 5.1/5.2:
- 5.1.1a: Eenheid van de Kroon
- 5.1.1b: Veiligheid van de Staat
- 5.1.2a: Internationale betrekkingen
@@ -219,191 +218,191 @@ Each document in a WOO case MUST be individually assessed for disclosure with ma
- 5.1.2i: Goed functioneren van de Staat
- 5.2.1: Persoonlijke beleidsopvattingen
- 5.2.2: Persoonlijke beleidsopvattingen (intern beraad, geanonimiseerd mogelijk)
-- AND the weigeringsgrond MUST be stored as metadata on the document assessment
+- **AND** the weigeringsgrond MUST be stored as metadata on the document assessment
-#### Scenario 6.3: Weigeringsgrond for partially disclosed documents
-- GIVEN a document assessed as "Deels openbaar"
-- THEN the case worker MUST also select the applicable weigeringsgrond(en) for the redacted portions
-- AND these grounds MUST appear in the inventarislijst and decision document
+#### Scenario: Weigeringsgrond for partially disclosed documents
+- **GIVEN** a document assessed as "Deels openbaar"
+- **THEN** the case worker MUST also select the applicable weigeringsgrond(en) for the redacted portions
+- **AND** these grounds MUST appear in the inventarislijst and decision document
-#### Scenario 6.4: Assessment progress indicator
-- GIVEN 15 documents in a WOO dossier with 10 assessed and 5 pending
-- THEN the case detail MUST show a progress indicator: "10/15 documenten beoordeeld"
-- AND the progress MUST be visible in both the case detail and the case list overview
+#### Scenario: Assessment progress indicator
+- **GIVEN** 15 documents in a WOO dossier with 10 assessed and 5 pending
+- **THEN** the case detail MUST show a progress indicator: "10/15 documenten beoordeeld"
+- **AND** the progress MUST be visible in both the case detail and the case list overview
-#### Scenario 6.5: Bulk assessment for similar documents
-- GIVEN multiple documents that share the same assessment (e.g., all internal meeting notes are "Niet openbaar" under 5.2.1)
-- WHEN the case worker selects multiple documents and applies a bulk assessment
-- THEN all selected documents MUST receive the same assessment and weigeringsgrond
-- AND each individual document MUST still be editable afterwards
+#### Scenario: Bulk assessment for similar documents
+- **GIVEN** multiple documents that share the same assessment (e.g., all internal meeting notes are "Niet openbaar" under 5.2.1)
+- **WHEN** the case worker selects multiple documents and applies a bulk assessment
+- **THEN** all selected documents MUST receive the same assessment and weigeringsgrond
+- **AND** each individual document MUST still be editable afterwards
-### Requirement 7: Redaction via Docudesk integration
+### Requirement: Redaction via Docudesk integration
Documents assessed as "Deels openbaar" MUST be routable to Docudesk for AI-assisted anonymization with human review.
-#### Scenario 7.1: Send document for redaction
-- GIVEN a document assessed as "Deels openbaar"
-- WHEN the case worker clicks "Anonimiseren"
-- THEN the document MUST be sent to Docudesk's anonymization pipeline via OpenConnector or direct API call
-- AND the case worker MUST be able to specify which entity types to detect (names, BSN, addresses, phone numbers)
-
-#### Scenario 7.2: Review AI-detected entities
-- GIVEN Docudesk returns a document with 23 detected entities highlighted
-- WHEN the case worker opens the redaction review
-- THEN each detected entity MUST be shown with its type (naam, BSN, adres) and the proposed redaction
-- AND the case worker MUST be able to accept, reject, or modify each proposed redaction
-- AND manually add redactions that the AI missed
-
-#### Scenario 7.3: Store anonymized version
-- GIVEN the case worker finalizes the redaction review
-- WHEN they confirm the redactions
-- THEN the anonymized document MUST be stored as a new file in Nextcloud Files linked to the case
-- AND the original (unredacted) document MUST be preserved but marked as "niet openbaar"
-- AND the link between original and anonymized version MUST be tracked
-
-#### Scenario 7.4: Batch redaction
-- GIVEN 8 documents assessed as "Deels openbaar"
-- WHEN the case worker selects "Batch anonimiseren"
-- THEN all 8 documents MUST be sent to Docudesk simultaneously
-- AND the system MUST track progress per document and notify when each is ready for review
-
-#### Scenario 7.5: Complete redaction stage gate
-- GIVEN a WOO case in stage "Lakken / Anonimiseren"
-- WHEN the case worker attempts to advance to "Besluit"
-- THEN the system MUST verify that ALL documents assessed as "Deels openbaar" have finalized anonymized versions
-- AND block advancement if any document is still pending redaction
-
-### Requirement 8: WOO decision (besluit)
+#### Scenario: Send document for redaction
+- **GIVEN** a document assessed as "Deels openbaar"
+- **WHEN** the case worker clicks "Anonimiseren"
+- **THEN** the document MUST be sent to Docudesk's anonymization pipeline via OpenConnector or direct API call
+- **AND** the case worker MUST be able to specify which entity types to detect (names, BSN, addresses, phone numbers)
+
+#### Scenario: Review AI-detected entities
+- **GIVEN** Docudesk returns a document with 23 detected entities highlighted
+- **WHEN** the case worker opens the redaction review
+- **THEN** each detected entity MUST be shown with its type (naam, BSN, adres) and the proposed redaction
+- **AND** the case worker MUST be able to accept, reject, or modify each proposed redaction
+- **AND** manually add redactions that the AI missed
+
+#### Scenario: Store anonymized version
+- **GIVEN** the case worker finalizes the redaction review
+- **WHEN** they confirm the redactions
+- **THEN** the anonymized document MUST be stored as a new file in Nextcloud Files linked to the case
+- **AND** the original (unredacted) document MUST be preserved but marked as "niet openbaar"
+- **AND** the link between original and anonymized version MUST be tracked
+
+#### Scenario: Batch redaction
+- **GIVEN** 8 documents assessed as "Deels openbaar"
+- **WHEN** the case worker selects "Batch anonimiseren"
+- **THEN** all 8 documents MUST be sent to Docudesk simultaneously
+- **AND** the system MUST track progress per document and notify when each is ready for review
+
+#### Scenario: Complete redaction stage gate
+- **GIVEN** a WOO case in stage "Lakken / Anonimiseren"
+- **WHEN** the case worker attempts to advance to "Besluit"
+- **THEN** the system MUST verify that ALL documents assessed as "Deels openbaar" have finalized anonymized versions
+- **AND** block advancement if any document is still pending redaction
+
+### Requirement: WOO decision (besluit)
The system MUST support formal WOO decision recording with per-document disposition.
-#### Scenario 8.1: Record WOO decision
-- GIVEN a WOO case in stage "Besluit"
-- WHEN the case worker opens the decision form
-- THEN the form MUST include:
+#### Scenario: Record WOO decision
+- **GIVEN** a WOO case in stage "Besluit"
+- **WHEN** the case worker opens the decision form
+- **THEN** the form MUST include:
- **Besluitdatum** (date, defaults to today)
- **Besluit samenvatting** (text summary of the decision)
- **Per-document disposition list** (auto-populated from assessments)
-- AND the decision MUST be stored using the `decision` schema with decisionType "WOO Besluit"
+- **AND** the decision MUST be stored using the `decision` schema with decisionType "WOO Besluit"
-#### Scenario 8.2: Generate decision document (beschikking)
-- GIVEN a completed WOO decision
-- WHEN the case worker clicks "Genereer beschikking"
-- THEN the system MUST generate a PDF decision letter containing:
+#### Scenario: Generate decision document (beschikking)
+- **GIVEN** a completed WOO decision
+- **WHEN** the case worker clicks "Genereer beschikking"
+- **THEN** the system MUST generate a PDF decision letter containing:
- Municipality header and logo
- Verzoeker details
- Summary of the request (onderwerp, periode)
- Decision per document (openbaar/deels openbaar/niet openbaar with weigeringsgrond)
- Legal basis and bezwaarclausule
- Inventarislijst as appendix
-- AND the generated document MUST be stored as a case document
-
-#### Scenario 8.3: Include bezwaarclausule
-- GIVEN a generated WOO beschikking
-- THEN the document MUST include a standard bezwaarclausule informing the verzoeker of their right to object within 6 weeks
-- AND the bezwaar deadline MUST be calculated and stored on the case for tracking
-
-#### Scenario 8.4: Decision approval workflow
-- GIVEN a WOO decision drafted by a case worker
-- WHEN the case requires approval (configured per zaaktype)
-- THEN the decision MUST be routed to an authorized approver (manager/bestuurder)
-- AND the approver MUST be able to approve, reject with comments, or request changes
-- AND only approved decisions can advance the case to "Publicatie"
-
-#### Scenario 8.5: Decision notification to verzoeker
-- GIVEN an approved WOO decision
-- WHEN the case worker sends the decision
-- THEN the beschikking MUST be sent to the verzoeker via their preferred channel (email, post, or Mijn Overheid Berichtenbox)
-- AND the sending MUST be recorded in the audit trail
-
-### Requirement 9: Publication to reading room
+- **AND** the generated document MUST be stored as a case document
+
+#### Scenario: Include bezwaarclausule
+- **GIVEN** a generated WOO beschikking
+- **THEN** the document MUST include a standard bezwaarclausule informing the verzoeker of their right to object within 6 weeks
+- **AND** the bezwaar deadline MUST be calculated and stored on the case for tracking
+
+#### Scenario: Decision approval workflow
+- **GIVEN** a WOO decision drafted by a case worker
+- **WHEN** the case requires approval (configured per zaaktype)
+- **THEN** the decision MUST be routed to an authorized approver (manager/bestuurder)
+- **AND** the approver MUST be able to approve, reject with comments, or request changes
+- **AND** only approved decisions can advance the case to "Publicatie"
+
+#### Scenario: Decision notification to verzoeker
+- **GIVEN** an approved WOO decision
+- **WHEN** the case worker sends the decision
+- **THEN** the beschikking MUST be sent to the verzoeker via their preferred channel (email, post, or Mijn Overheid Berichtenbox)
+- **AND** the sending MUST be recorded in the audit trail
+
+### Requirement: Publication to reading room
The system MUST support publication of WOO documents to a publicly accessible reading room.
-#### Scenario 9.1: Prepare publication package
-- GIVEN a completed WOO case with an approved decision
-- WHEN the case worker triggers "Publicatie voorbereiden"
-- THEN the system MUST assemble a publication package containing:
+#### Scenario: Prepare publication package
+- **GIVEN** a completed WOO case with an approved decision
+- **WHEN** the case worker triggers "Publicatie voorbereiden"
+- **THEN** the system MUST assemble a publication package containing:
- The decision document (beschikking)
- The inventarislijst
- All documents assessed as "Openbaar"
- Anonymized versions of "Deels openbaar" documents
-- AND the package MUST exclude all "Niet openbaar" documents and original (unredacted) versions
-
-#### Scenario 9.2: Publish to public URL
-- GIVEN a prepared publication package
-- WHEN the case worker triggers "Publiceren"
-- THEN the documents MUST be published to a publicly accessible URL (reading room)
-- AND the URL MUST be shareable and not require authentication
-- AND the publication MUST include a cover page with the case summary and inventarislijst
-
-#### Scenario 9.3: Publish to PLOOI
-- GIVEN PLOOI (Platform Open Overheidsinformatie) integration is configured
-- WHEN the case worker triggers publication
-- THEN the system MUST also push the publication package to PLOOI via its API
-- AND store the PLOOI reference identifier on the case
-
-#### Scenario 9.4: Publication audit trail
-- GIVEN a WOO publication
-- THEN the audit trail MUST record: publication date, published documents list, public URL, and the user who triggered publication
-- AND any subsequent corrections or retractions MUST also be logged
-
-#### Scenario 9.5: Retract published document
-- GIVEN a published WOO dossier
-- WHEN a court ruling or new assessment requires retraction of a specific document
-- THEN the case worker MUST be able to retract individual documents from the reading room
-- AND the retraction MUST be logged with reason and the reading room MUST show "Ingetrokken" for that document
-
-### Requirement 10: Bezwaar (objection) period tracking
+- **AND** the package MUST exclude all "Niet openbaar" documents and original (unredacted) versions
+
+#### Scenario: Publish to public URL
+- **GIVEN** a prepared publication package
+- **WHEN** the case worker triggers "Publiceren"
+- **THEN** the documents MUST be published to a publicly accessible URL (reading room)
+- **AND** the URL MUST be shareable and not require authentication
+- **AND** the publication MUST include a cover page with the case summary and inventarislijst
+
+#### Scenario: Publish to PLOOI
+- **GIVEN** PLOOI (Platform Open Overheidsinformatie) integration is configured
+- **WHEN** the case worker triggers publication
+- **THEN** the system MUST also push the publication package to PLOOI via its API
+- **AND** store the PLOOI reference identifier on the case
+
+#### Scenario: Publication audit trail
+- **GIVEN** a WOO publication
+- **THEN** the audit trail MUST record: publication date, published documents list, public URL, and the user who triggered publication
+- **AND** any subsequent corrections or retractions MUST also be logged
+
+#### Scenario: Retract published document
+- **GIVEN** a published WOO dossier
+- **WHEN** a court ruling or new assessment requires retraction of a specific document
+- **THEN** the case worker MUST be able to retract individual documents from the reading room
+- **AND** the retraction MUST be logged with reason and the reading room MUST show "Ingetrokken" for that document
+
+### Requirement: Bezwaar (objection) period tracking
The system MUST track the objection period after a WOO decision and handle incoming objections.
-#### Scenario 10.1: Calculate bezwaar deadline
-- GIVEN a WOO decision sent on "2026-04-15"
-- THEN the system MUST calculate the bezwaar deadline as 6 weeks: "2026-05-27"
-- AND display the bezwaar deadline in the case detail after the decision stage
-
-#### Scenario 10.2: Register incoming bezwaar
-- GIVEN a WOO case in the bezwaar period
-- WHEN an objection is received from the verzoeker
-- THEN the case worker MUST be able to register the bezwaar with: ontvangstdatum, bezwaargronden (text), and the objecting party's details
-- AND the case MUST be reopened or a linked bezwaar case MUST be created
-
-#### Scenario 10.3: Bezwaar period expired without objection
-- GIVEN a WOO case whose bezwaar deadline has passed with no objection registered
-- THEN the system MUST mark the case as "Onherroepelijk" (irrevocable)
-- AND the case MAY be automatically archived per the configured retention policy
-
-#### Scenario 10.4: Bezwaar notification to case worker
-- GIVEN a WOO case approaching the end of its bezwaar period (7 days remaining)
-- THEN the system MUST notify the assigned case worker
-- AND the notification MUST appear in the Nextcloud notification center
-
-#### Scenario 10.5: Bezwaar extends case lifecycle
-- GIVEN a bezwaar is registered on a WOO case
-- THEN the case status MUST change to "Bezwaar in behandeling"
-- AND new tasks MUST be created for the bezwaar handling workflow (hoorplicht, heroverweging, beslissing op bezwaar)
-
-### Requirement 11: WOO reporting and statistics
+#### Scenario: Calculate bezwaar deadline
+- **GIVEN** a WOO decision sent on "2026-04-15"
+- **THEN** the system MUST calculate the bezwaar deadline as 6 weeks: "2026-05-27"
+- **AND** display the bezwaar deadline in the case detail after the decision stage
+
+#### Scenario: Register incoming bezwaar
+- **GIVEN** a WOO case in the bezwaar period
+- **WHEN** an objection is received from the verzoeker
+- **THEN** the case worker MUST be able to register the bezwaar with: ontvangstdatum, bezwaargronden (text), and the objecting party's details
+- **AND** the case MUST be reopened or a linked bezwaar case MUST be created
+
+#### Scenario: Bezwaar period expired without objection
+- **GIVEN** a WOO case whose bezwaar deadline has passed with no objection registered
+- **THEN** the system MUST mark the case as "Onherroepelijk" (irrevocable)
+- **AND** the case MAY be automatically archived per the configured retention policy
+
+#### Scenario: Bezwaar notification to case worker
+- **GIVEN** a WOO case approaching the end of its bezwaar period (7 days remaining)
+- **THEN** the system MUST notify the assigned case worker
+- **AND** the notification MUST appear in the Nextcloud notification center
+
+#### Scenario: Bezwaar extends case lifecycle
+- **GIVEN** a bezwaar is registered on a WOO case
+- **THEN** the case status MUST change to "Bezwaar in behandeling"
+- **AND** new tasks MUST be created for the bezwaar handling workflow (hoorplicht, heroverweging, beslissing op bezwaar)
+
+### Requirement: WOO reporting and statistics
The system MUST provide reporting on WOO case processing for management oversight and statutory reporting.
-#### Scenario 11.1: WOO dashboard widget
-- GIVEN the Procest dashboard
-- THEN a "WOO Overzicht" widget MUST display:
+#### Scenario: WOO dashboard widget
+- **GIVEN** the Procest dashboard
+- **THEN** a "WOO Overzicht" widget MUST display:
- Total active WOO cases
- Cases approaching deadline (< 7 days)
- Cases past deadline
- Average processing time (completed cases)
- Cases per stage distribution
-#### Scenario 11.2: Annual WOO report data
-- GIVEN the admin requests an annual WOO report for 2026
-- THEN the system MUST export:
+#### Scenario: Annual WOO report data
+- **GIVEN** the admin requests an annual WOO report for 2026
+- **THEN** the system MUST export:
- Total WOO requests received
- Total processed within deadline vs. past deadline
- Breakdown by assessment type (openbaar/deels openbaar/niet openbaar)
- Most common weigeringsgronden
- Average processing time
-#### Scenario 11.3: Per-case statistics
-- GIVEN a completed WOO case
-- THEN the case detail MUST show:
+#### Scenario: Per-case statistics
+- **GIVEN** a completed WOO case
+- **THEN** the case detail MUST show:
- Total processing time (days)
- Number of documents assessed per category
- Number of documents redacted
diff --git a/openspec/changes/zaak-intake-flow/specs/zaak-intake-flow/spec.md b/openspec/changes/zaak-intake-flow/specs/zaak-intake-flow/spec.md
deleted file mode 100644
index 8247a22c..00000000
--- a/openspec/changes/zaak-intake-flow/specs/zaak-intake-flow/spec.md
+++ /dev/null
@@ -1,521 +0,0 @@
----
-status: implemented
----
-# Zaak Intake Flow Specification
-
-## Purpose
-
-The zaak intake flow governs what happens after a case is initiated -- whether from Open Formulieren, DSO/Omgevingsloket, manual entry, or API call. It handles automatic zaaktype assignment, status initialization, initial task creation, notification to the assigned behandelaar, and linking of the initiator. This is the bridge between external input and the internal case lifecycle.
-
-**Tender demand**: 61% of tenders (42/69) require formulieren/intake capabilities. Automatic case creation from external submissions is a baseline expectation.
-**Standards**: ZGW Zaken API (`zaak-create`), StUF-ZKN (`creeerZaak_Lk01`), CMMN 1.1 (CasePlanModel instantiation)
-**Feature tier**: MVP (manual + API intake, zaaktype assignment, status init, behandelaar notification), V1 (Open Formulieren integration, DSO intake, duplicate detection, batch intake, e-mail intake)
-
-## Intake Channels
-
-| Channel | Protocol | Description | Tier |
-|---------|----------|-------------|------|
-| Manual entry | Procest UI | Behandelaar creates case via "New Case" form | MVP |
-| ZGW API | REST (`POST /zaken/api/v1/zaken`) | External system creates case via ZGW-compliant endpoint | MVP |
-| Open Formulieren | Webhook / ZGW API | Citizen submits e-form with DigiD; form engine calls zaak-create | V1 |
-| DSO/Omgevingsloket | StUF-LVO / REST | Omgevingswet application forwarded from DSO | V1 |
-| E-mail | IMAP trigger | Incoming e-mail parsed and converted to case (via n8n) | V1 |
-| Bulk import | CSV/JSON upload | Batch case creation for migration or seasonal intake | V1 |
-
-## Requirements
-
----
-
-### REQ-INTAKE-01: Manual Case Creation
-
-The system MUST support creating cases via the Procest UI with a guided creation form.
-
-**Feature tier**: MVP
-
-
-#### Scenario INTAKE-01a: Create case via dialog
-
-- GIVEN a user with case management access
-- WHEN the user clicks "+ New Case" on the dashboard or case list
-- THEN the system MUST display the `CaseCreateDialog` with fields: case type (dropdown), title (text), description (text area)
-- AND the case type dropdown MUST only show published, currently valid case types
-- AND the default case type (if configured) MUST be pre-selected
-
-#### Scenario INTAKE-01b: Case type selection shows metadata
-
-- GIVEN the case type dropdown is open
-- WHEN the user hovers over or selects "Omgevingsvergunning"
-- THEN the system SHOULD display: processing deadline ("56 days"), description, and number of required document types
-- AND this helps the user select the correct case type
-
-#### Scenario INTAKE-01c: Manual case submission succeeds
-
-- GIVEN the user has selected case type "Subsidieaanvraag" and entered title "Innovatiesubsidie 2026"
-- WHEN the user clicks "Create"
-- THEN the system MUST create the case in the `procest` register with the `case` schema
-- AND the `identifier` MUST be auto-generated
-- AND the `startDate` MUST be set to today
-- AND the `deadline` MUST be calculated as today + processingDeadline
-- AND the `status` MUST be set to the first status type by order
-- AND the user MUST be navigated to the new case's detail view
-
-#### Scenario INTAKE-01d: Manual case with optional description
-
-- GIVEN the user creates a case with only case type and title (description is empty)
-- WHEN the case is created
-- THEN the `description` field MUST be stored as empty/null
-- AND the case MUST be created successfully
-
-#### Scenario INTAKE-01e: Cancel case creation
-
-- GIVEN the case creation dialog is open
-- WHEN the user clicks "Cancel"
-- THEN the dialog MUST close without creating a case
-- AND no data MUST be persisted
-
----
-
-### REQ-INTAKE-02: API-Driven Case Creation
-
-The system MUST accept case creation requests via the ZGW Zaken API endpoint. Upon receiving a valid request, the system MUST instantiate the case with all behavioral controls from the case type.
-
-**Feature tier**: MVP
-
-
-#### Scenario INTAKE-02a: Successful API intake
-
-- GIVEN a published case type "Omgevingsvergunning" with `processingDeadline = "P56D"` and initial status "Ontvangen"
-- WHEN an external system sends `POST /zaken/api/v1/zaken` with `zaaktype`, `omschrijving`, and `startdatum`
-- THEN the system MUST create the case in the `procest` register
-- AND `identifier` MUST be auto-generated (format: `YYYY-NNN`)
-- AND `deadline` MUST be calculated as `startdatum + P56D`
-- AND `status` MUST be set to the first status type by `order`
-- AND the system MUST return HTTP 201 with the case resource in ZGW format
-
-#### Scenario INTAKE-02b: Reject intake with invalid zaaktype
-
-- GIVEN a zaaktype URL that references a draft or expired case type
-- WHEN an external system sends a create request
-- THEN the system MUST return HTTP 400 with error: "Zaaktype is not published or not within its validity window"
-
-#### Scenario INTAKE-02c: API intake with all optional fields
-
-- GIVEN a valid create request including: zaaktype, omschrijving, startdatum, toelichting, vertrouwelijkheidaanduiding, zaakgeometrie
-- WHEN the system processes the request
-- THEN all provided fields MUST be mapped to the case properties (description, confidentiality, geometry)
-- AND fields not provided MUST use defaults from the case type
-
-#### Scenario INTAKE-02d: API authentication required
-
-- GIVEN a create request without valid authentication (JWT or Basic Auth)
-- WHEN the system receives the request
-- THEN the system MUST return HTTP 401 Unauthorized
-- AND no case MUST be created
-
-#### Scenario INTAKE-02e: API intake returns ZGW-compliant response
-
-- GIVEN a successful case creation via API
-- WHEN the system returns the response
-- THEN the response MUST include: `url` (self reference), `uuid`, `identificatie`, `omschrijving`, `zaaktype` (URL), `startdatum`, `status` (URL to first status), `einddatumGepland`
-- AND the response MUST conform to the ZGW Zaken API response schema
-
----
-
-### REQ-INTAKE-03: Automatic Behandelaar Assignment
-
-The system SHALL support automatic assignment of a behandelaar based on case type configuration.
-
-**Feature tier**: MVP
-
-
-#### Scenario INTAKE-03a: Default handler from case type
-
-- GIVEN a case type "Subsidieaanvraag" with `defaultAssignee = "team-subsidies"` (a Nextcloud group)
-- WHEN a new case of this type is created via any channel
-- THEN the system MUST assign the case to the configured default assignee
-- AND a Nextcloud notification MUST be sent: "Nieuwe zaak toegewezen: [title]"
-
-#### Scenario INTAKE-03b: Round-robin assignment within team
-
-- GIVEN a case type with `assignmentStrategy = "round-robin"` and team members ["Jan", "Maria", "Pieter"]
-- AND Jan has 5 open cases, Maria has 3, Pieter has 4
-- WHEN a new case is created
-- THEN the system SHOULD assign to Maria (lowest workload)
-
-#### Scenario INTAKE-03c: No default assignee configured
-
-- GIVEN a case type with no `defaultAssignee` configured
-- WHEN a new case is created
-- THEN the case MUST be created without an assignee
-- AND the case MUST appear as "Unassigned" in the case list
-- AND the dashboard MUST count this case in the "unassigned" category
-
-#### Scenario INTAKE-03d: Assignment notification delivery
-
-- GIVEN a case assigned to handler "Jan de Vries"
-- WHEN the assignment is made
-- THEN the system MUST send a Nextcloud notification to Jan
-- AND the notification MUST include: case title, case type, and a link to the case detail
-- AND the notification MUST be visible in Jan's Nextcloud notification panel
-
-#### Scenario INTAKE-03e: Group assignment shows in case list
-
-- GIVEN a case assigned to group "team-subsidies" (3 members)
-- WHEN any member of team-subsidies views the case list
-- THEN the case MUST appear in their "My Work" view
-- AND the handler field MUST show "team-subsidies" until a specific person claims the case
-
----
-
-### REQ-INTAKE-04: Initiator Role Creation
-
-The system MUST support linking an initiator (aanvrager) to a case during intake.
-
-**Feature tier**: MVP
-
-
-#### Scenario INTAKE-04a: Manual initiator assignment
-
-- GIVEN a case created via manual entry
-- WHEN the handler opens the participants panel and clicks "Add Participant"
-- THEN the handler MUST be able to select role "Aanvrager"
-- AND search for a person (BRP lookup) or enter a name manually
-- AND the selected person MUST be linked to the case with role "Aanvrager"
-
-#### Scenario INTAKE-04b: API intake with initiator BSN
-
-- GIVEN a ZGW API case creation request that includes a subsequent `POST /zaken/api/v1/rollen` with `betrokkeneType = "natuurlijk_persoon"` and BSN
-- WHEN the system processes the request
-- THEN the initiator MUST be created as a case participant with role "Aanvrager"
-- AND the BSN MUST be stored (encrypted per AVG requirements)
-- AND the initiator name MUST be resolved from BRP if available
-
-#### Scenario INTAKE-04c: API intake with initiator organization (KVK)
-
-- GIVEN a ZGW API role creation with `betrokkeneType = "niet_natuurlijk_persoon"` and KVK number
-- WHEN the system processes the request
-- THEN the organization MUST be linked as a case participant
-- AND the organization name MUST be resolved from KVK if available
-
----
-
-### REQ-INTAKE-05: Initial Task Creation
-
-The system SHALL support automatic creation of initial tasks when a case is created, based on the case type configuration.
-
-**Feature tier**: V1
-
-
-#### Scenario INTAKE-05a: Auto-create intake checklist tasks
-
-- GIVEN a case type "Omgevingsvergunning" with initial tasks configured: ["Ontvangstbevestiging versturen", "Compleetheid toetsen", "Leges berekenen"]
-- WHEN a new case of this type is created
-- THEN the system MUST create 3 tasks linked to the case
-- AND each task MUST have status "available" and be assigned to the case handler
-- AND each task MUST have a due date relative to the case start date (if configured)
-
-#### Scenario INTAKE-05b: Task template with relative due date
-
-- GIVEN a task template "Ontvangstbevestiging versturen" with relativeDueDate = "P3D"
-- WHEN the task is auto-created for a case starting today
-- THEN the task dueDate MUST be set to today + 3 days
-
-#### Scenario INTAKE-05c: No initial tasks configured
-
-- GIVEN a case type "Melding openbare ruimte" with no initial task templates
-- WHEN a case is created
-- THEN no tasks MUST be auto-created
-- AND the tasks section in the case detail MUST show "No tasks"
-
-#### Scenario INTAKE-05d: Initial tasks assigned to handler
-
-- GIVEN a case type with initial tasks and defaultAssignee = "Jan de Vries"
-- WHEN a case is created and Jan is assigned as handler
-- THEN all auto-created tasks MUST be assigned to Jan
-- AND Jan MUST receive a notification for each task (or a single summary notification)
-
----
-
-### REQ-INTAKE-06: Open Formulieren Integration
-
-The system MUST support receiving case submissions from Open Formulieren via ZGW API callback.
-
-**Feature tier**: V1
-
-
-#### Scenario INTAKE-06a: E-form submission creates case with attachments
-
-- GIVEN Open Formulieren configured with Procest as ZGW backend
-- AND a citizen submits form "Aanvraag omgevingsvergunning" with DigiD authentication
-- WHEN the form engine calls `POST /zaken/api/v1/zaken` followed by document uploads
-- THEN the system MUST create the case with the citizen as initiator (role type "Aanvrager")
-- AND uploaded documents MUST be linked to the case
-- AND BSN from DigiD MUST be stored on the initiator role (encrypted, AVG-compliant)
-- AND the system MUST send an ontvangstbevestiging notification
-
-#### Scenario INTAKE-06b: Form data mapped to custom properties
-
-- GIVEN a form that submits structured data (bouwkosten, oppervlakte, adres)
-- WHEN the case is created
-- THEN the system MUST map form fields to case property definitions where names match
-- AND unmapped fields MUST be stored as case metadata (not silently discarded)
-
-#### Scenario INTAKE-06c: Open Formulieren with file attachments
-
-- GIVEN a form submission with 3 PDF attachments (bouwtekening, constructieberekening, situatietekening)
-- WHEN the form engine uploads these via `POST /documenten/api/v1/enkelvoudiginformatieobjecten`
-- THEN each document MUST be stored in Nextcloud Files (IRootFolder)
-- AND each document MUST be linked to the case via `zaakinformatieobjecten`
-- AND the document type MUST be matched against the case type's document type configuration
-
-#### Scenario INTAKE-06d: DigiD BSN encrypted storage
-
-- GIVEN a citizen authenticates with DigiD (BSN 999993653)
-- WHEN the BSN is stored on the initiator role
-- THEN the BSN MUST be encrypted at rest
-- AND the BSN MUST only be accessible to users with the appropriate RBAC permission
-- AND access to BSN MUST be logged for AVG compliance
-
----
-
-### REQ-INTAKE-07: Duplicate Detection
-
-The system SHALL detect potential duplicate submissions to prevent double case creation.
-
-**Feature tier**: V1
-
-
-#### Scenario INTAKE-07a: Warn on potential duplicate
-
-- GIVEN an existing case "Bouwvergunning Keizersgracht 100" for BSN 123456789
-- WHEN a new submission arrives for the same BSN with similar title within 24 hours
-- THEN the system MUST flag the intake as a potential duplicate
-- AND the behandelaar MUST be notified: "Mogelijke dubbele aanvraag gedetecteerd"
-- AND the case MUST still be created (not blocked) but marked for review
-
-#### Scenario INTAKE-07b: Duplicate detection criteria
-
-- GIVEN the duplicate detection system
-- THEN a potential duplicate MUST be flagged when ANY of the following match within 24 hours:
- - Same BSN + same case type
- - Same BAG address + same case type
- - Title similarity > 80% (fuzzy match) + same case type
-- AND the handler MUST be able to dismiss the duplicate flag after review
-
-#### Scenario INTAKE-07c: Duplicate detection for API intake
-
-- GIVEN two API calls creating cases with the same zaaktype and similar omschrijving within 1 minute
-- WHEN the second case is created
-- THEN the system MUST flag it as a potential duplicate
-- AND the API response MUST include a header `X-Duplicate-Warning: possible` (but still return 201)
-
-#### Scenario INTAKE-07d: Dismiss duplicate flag
-
-- GIVEN a case flagged as potential duplicate
-- WHEN the handler reviews both cases and determines they are distinct
-- THEN the handler MUST be able to click "Not a duplicate" to dismiss the flag
-- AND the flag MUST be removed from the case
-- AND the audit trail MUST record: "Duplicate flag dismissed by [handler]"
-
----
-
-### REQ-INTAKE-08: Intake Audit Trail
-
-The system MUST record the intake channel and source metadata in the case audit trail.
-
-**Feature tier**: MVP
-
-
-#### Scenario INTAKE-08a: Record intake source for manual entry
-
-- GIVEN a case created via the Procest UI
-- WHEN the case is stored
-- THEN the audit trail MUST record: intake channel "manual", created by user name, creation timestamp
-
-#### Scenario INTAKE-08b: Record intake source for API intake
-
-- GIVEN a case created via the ZGW API
-- WHEN the case is stored
-- THEN the audit trail MUST record: intake channel "zgw-api", authenticated client ID, source IP, creation timestamp
-
-#### Scenario INTAKE-08c: Record intake source for Open Formulieren
-
-- GIVEN a case created via Open Formulieren
-- WHEN the case is stored
-- THEN the audit trail MUST record: intake channel "open-formulieren", source form ID, submission timestamp, initiator BSN (hashed)
-- AND this information MUST be queryable for reporting (e.g., "how many cases came from e-forms this month")
-
-#### Scenario INTAKE-08d: Intake channel visible on case detail
-
-- GIVEN a case created via Open Formulieren
-- WHEN the handler views the case detail
-- THEN the case info panel MUST show the intake channel (e.g., "Bron: Open Formulieren")
-- AND clicking the source link SHOULD navigate to the original form submission (if available)
-
----
-
-### REQ-INTAKE-09: E-mail Intake
-
-The system SHALL support creating cases from incoming e-mails via n8n workflow automation.
-
-**Feature tier**: V1
-
-
-#### Scenario INTAKE-09a: E-mail triggers case creation
-
-- GIVEN an n8n workflow configured to monitor an IMAP mailbox (e.g., info@gemeente.nl)
-- AND the workflow contains rules to categorize e-mails by subject keywords
-- WHEN an e-mail arrives with subject "Klacht over geluidsoverlast Keizersgracht"
-- THEN the workflow MUST create a case of type "Klacht behandeling" via the ZGW API
-- AND the e-mail body MUST be stored as the case description
-- AND e-mail attachments MUST be uploaded as case documents
-
-#### Scenario INTAKE-09b: E-mail sender as initiator
-
-- GIVEN an incoming e-mail from "petra.jansen@example.nl"
-- WHEN the case is created
-- THEN the sender MUST be linked as initiator (role "Aanvrager") with their e-mail address
-- AND if the e-mail matches a known contact (Pipelinq client), the system SHOULD auto-link
-
-#### Scenario INTAKE-09c: E-mail intake with unknown category
-
-- GIVEN an incoming e-mail that does not match any categorization rules
-- WHEN the n8n workflow processes it
-- THEN the workflow MUST create a case with a default case type (e.g., "Melding openbare ruimte")
-- AND the case MUST be flagged for manual categorization by the behandelaar
-
----
-
-### REQ-INTAKE-10: Bulk Import
-
-The system SHALL support batch case creation via CSV or JSON file upload for migration or seasonal intake scenarios.
-
-**Feature tier**: V1
-
-
-#### Scenario INTAKE-10a: CSV bulk import
-
-- GIVEN an admin uploads a CSV file with columns: title, caseType, description, startDate, assignee
-- AND the CSV contains 50 rows
-- WHEN the admin initiates the import
-- THEN the system MUST validate all rows before creating any cases
-- AND validation errors MUST be reported per row (e.g., "Row 12: invalid case type 'Onbekend'")
-- AND the admin MUST confirm import after reviewing the validation report
-
-#### Scenario INTAKE-10b: Bulk import with validation errors
-
-- GIVEN a CSV with 50 rows, 3 of which have invalid case types
-- WHEN the admin views the validation report
-- THEN the system MUST show: "47 rows valid, 3 rows with errors"
-- AND the admin MUST choose: import valid rows only, fix errors and retry, or cancel
-
-#### Scenario INTAKE-10c: Bulk import progress
-
-- GIVEN 47 valid cases being imported
-- WHEN the import is in progress
-- THEN the system MUST show a progress indicator (e.g., "23/47 cases created...")
-- AND the system MUST handle failures gracefully (partial import is acceptable, failed rows are reported)
-
----
-
-### REQ-INTAKE-11: Intake Channel Selection for Manual Entry
-
-The manual case creation form MUST allow recording the original intake channel even when the case is entered manually.
-
-**Feature tier**: MVP
-
-
-#### Scenario INTAKE-11a: Record intake channel on manual case
-
-- GIVEN a behandelaar creating a case from a phone call
-- WHEN the case creation form is displayed
-- THEN an optional "Intake Channel" dropdown MUST be available with options: "Balie" (counter), "Telefoon" (phone), "E-mail", "Post", "Website", "Overig" (other)
-- AND the selected channel MUST be stored on the case metadata
-
-#### Scenario INTAKE-11b: Default channel for manual entry
-
-- GIVEN a case created via the UI without selecting an intake channel
-- WHEN the case is stored
-- THEN the intake channel MUST default to "Manual" in the audit trail
-- AND the case info panel MUST show "Bron: Handmatig"
-
-#### Scenario INTAKE-11c: Intake channel reporting
-
-- GIVEN 50 cases created this month via various channels
-- WHEN an admin views intake channel statistics
-- THEN the system MUST be able to aggregate: X cases from Balie, Y from Telefoon, Z from E-mail, etc.
-- AND this data MUST be available via the OpenRegister API for reporting tools
-
-## Dependencies
-
-- **Case Management spec** (`../case-management/spec.md`): Intake creates cases; all case validation rules apply.
-- **Case Types spec** (`../case-types/spec.md`): Case type controls intake behavior (default assignee, initial tasks, required fields).
-- **Task Management spec** (`../task-management/spec.md`): Initial tasks are created per task spec.
-- **Roles & Decisions spec** (`../roles-decisions/spec.md`): Initiator role is created during intake.
-- **OpenRegister**: All case data stored as OpenRegister objects.
-- **OpenConnector**: ZGW API endpoint routing and StUF translation.
-- **n8n**: E-mail intake workflow automation.
-
----
-
-### Using Mock Register Data
-
-This spec depends on the **BAG** and **DSO** mock registers for testing address validation and DSO intake (REQ-INTAKE-06, V1).
-
-**Loading the registers:**
-```bash
-# Load BAG register (32 addresses + 21 objects + 21 buildings, register slug: "bag")
-docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/bag_register.json
-
-# Load DSO register (53 records, register slug: "dso", schemas: "activiteit", "locatie", "omgevingsdocument", "vergunningaanvraag")
-docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/dso_register.json
-
-# Load BRP register for initiator BSN linking
-docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json
-```
-
-**Test data for this spec's use cases:**
-- **DSO intake (REQ-INTAKE-06)**: Use DSO `vergunningaanvraag` records to test omgevingsvergunning case creation with activiteiten, locatie, and bijlagen
-- **BAG address validation**: Use BAG `nummeraanduiding` records to test address resolution in form-to-case mapping
-- **Initiator with BSN**: BSN `999993653` (Suzanne Moulin) -- test initiator role creation with BSN from Open Formulieren DigiD flow
-- **DSO activiteiten**: Use DSO `activiteit` records (e.g., "Dakkapel plaatsen", "Aanbouw") for activity-to-zaaktype mapping
-
-### Current Implementation Status
-
-**Partially implemented (manual intake + ZGW API intake). V1 features not implemented.**
-
-**Implemented (with file paths):**
-- **Manual case creation (REQ-INTAKE-01)**: `src/views/cases/CaseCreateDialog.vue` provides a UI form for creating new cases. Users select a case type, fill in title, description, and other fields. The case is created via the object store against OpenRegister.
-- **ZGW API case creation (REQ-INTAKE-02)**: `lib/Controller/ZrcController.php` provides `POST /api/zgw/zaken/v1/zaken` endpoint for external systems to create cases via ZGW-compliant API. Supports zaaktype reference, omschrijving, startdatum, and other ZGW fields.
-- **ZGW business rules**: `lib/Service/ZgwBusinessRulesService.php` and `lib/Service/ZgwZrcRulesService.php` implement validation rules for case creation, including zaaktype validation, status initialization, and field mapping.
-- **ZGW mapping**: `lib/Service/ZgwMappingService.php` handles bidirectional mapping between ZGW Dutch terminology and Procest English field names.
-- **ZGW auth**: `lib/Middleware/ZgwAuthMiddleware.php` provides JWT-based authentication for ZGW API endpoints, allowing external systems to authenticate.
-- **Case type validation**: `src/utils/caseTypeValidation.js` provides client-side validation for case type data. `lib/Service/ZgwZtcRulesService.php` validates zaaktype status (draft/published, validity window).
-- **Deadline calculation**: The case type's `processingDeadline` (ISO 8601 duration) is used to calculate the case deadline. `src/utils/durationHelpers.js` supports ISO 8601 duration parsing. `src/views/cases/components/DeadlinePanel.vue` displays the calculated deadline.
-- **Status initialization**: New cases get their status set to the first status type (by order) of the case type. Implemented in ZGW business rules and CaseCreateDialog.
-- **Audit trail**: Case creation is logged via OpenRegister's audit trail. The `auditTrailsPlugin()` in the object store captures creation events (REQ-INTAKE-08).
-- **ZGW notification**: `lib/Controller/NrcController.php` and `lib/Service/NotificatieService.php` support ZGW notification webhooks for case lifecycle events.
-- **Identifier generation**: Case identifiers can be auto-generated (format depends on configuration).
-
-**Not yet implemented:**
-- **REQ-INTAKE-03: Automatic behandelaar assignment**: No default assignee configuration on case types. No round-robin assignment strategy. Cases are created without an assignee unless manually set.
-- **REQ-INTAKE-04: Initiator role creation**: No automatic creation of initiator role during intake from API. The role must be manually added via the Participants section (or via separate ZGW rollen API call).
-- **REQ-INTAKE-05: Initial task creation (V1)**: No automatic task creation based on case type configuration. No task templates on case types.
-- **REQ-INTAKE-06: Open Formulieren integration (V1)**: No integration with Open Formulieren. No DigiD/BSN handling. No form-to-case field mapping.
-- **REQ-INTAKE-07: Duplicate detection (V1)**: No duplicate submission detection.
-- **REQ-INTAKE-09: E-mail intake (V1)**: No IMAP trigger or e-mail-to-case conversion.
-- **REQ-INTAKE-10: Bulk import (V1)**: No CSV/JSON bulk case creation.
-- **REQ-INTAKE-11: Intake channel selection**: No intake channel dropdown on the manual creation form.
-- **Assignment notification**: No Nextcloud notification sent when a case is assigned to a handler.
-
-### Standards & References
-
-- **ZGW Zaken API**: `POST /zaken/api/v1/zaken` implemented via `ZrcController.php`. Supports the ZGW case creation flow with zaaktype validation, status initialization, and field mapping.
-- **StUF-ZKN**: `creeerZaak_Lk01` message format not implemented. StUF translation would require OpenConnector.
-- **CMMN 1.1**: Case instantiation follows CasePlanModel patterns with status lifecycle initialization.
-- **Common Ground**: Intake channels (API, forms, DSO) align with Common Ground information layer principles.
-- **Open Formulieren**: VNG's open-source form engine for citizen-facing e-forms. Integration via ZGW API callback.
-- **DSO (Digitaal Stelsel Omgevingswet)**: Environmental law digital system for permit applications.
-- **DigiD/eHerkenning**: Dutch government authentication for citizens (DigiD) and organizations (eHerkenning). BSN handling requires AVG-compliant encryption.
-- **AVG/GDPR**: BSN storage must be encrypted, access logged, and retention limited.
-- **Competitor reference**: Dimpact ZAC integrates with Open Formulieren and SmartDocuments for intake. CaseFabric provides multi-channel intake with auto-categorization. XXllnc Zaken supports DSO intake and StUF-ZKN for legacy system compatibility.
diff --git a/openspec/changes/zaaktype-configuratie/specs/zaaktype-configuratie/spec.md b/openspec/changes/zaaktype-configuratie/specs/zaaktype-configuratie/spec.md
deleted file mode 100644
index b5c3b89c..00000000
--- a/openspec/changes/zaaktype-configuratie/specs/zaaktype-configuratie/spec.md
+++ /dev/null
@@ -1,506 +0,0 @@
----
-status: implemented
----
-# Zaaktype Configuratie Specification
-
-## Purpose
-
-Zaaktype Configuratie provides a zero-coding admin UI for configuring case types and all their behavioral components: status diagrams, checklists, required documents, deadlines, parafeerroutes, and property definitions. While the Case Types spec (`../case-types/spec.md`) defines the data model and validation rules, this spec covers the configuration UI and workflows that administrators use to set up and maintain case types without developer involvement.
-
-**Tender demand**: 23% of tenders (16/69) explicitly require zero-coding zaaktype configuration. Additionally, 36% of all tenders ask for "zero-coding configuratie" as a general principle. This is a key differentiator -- municipalities want to reduce leveranciersafhankelijkheid.
-**Relationship to existing specs**: This spec EXTENDS `case-types` (data model). It does NOT duplicate the data model or validation rules. It adds the admin UI and configuration workflows. Check `case-types` for all entity definitions.
-**Standards**: ZGW Catalogi API (ZaakType, StatusType, ResultaatType, InformatieObjectType), CMMN 1.1 (CaseDefinition)
-**Feature tier**: V1 (basic CRUD UI, status diagram editor, document type config, property definition config, role type config, result type config), V2 (visual flow designer, import/export, ZTC sync, versioning, test mode)
-
-## Requirements
-
----
-
-### REQ-ZTC-01: Case Type CRUD via Admin UI
-
-The system MUST provide an admin interface for creating, editing, and managing case types without code changes.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-01a: Create new case type
-
-- GIVEN an admin navigating to Procest Admin > Zaaktypen
-- WHEN the admin clicks "Nieuw zaaktype"
-- THEN a form MUST be displayed with fields from the case-types spec: title, description, processingDeadline (ISO 8601 duration picker), confidentiality, validFrom, validUntil
-- AND the case type MUST be created in draft status
-- AND the admin MUST be warned: "Dit zaaktype is nog een concept. Publiceer het om zaken te kunnen aanmaken."
-
-#### Scenario ZTC-01b: Edit existing case type
-
-- GIVEN a published case type "Omgevingsvergunning" with 10 active cases
-- WHEN the admin edits the case type
-- THEN the system MUST warn: "Er zijn 10 actieve zaken van dit type. Wijzigingen gelden alleen voor nieuwe zaken."
-- AND the admin MAY choose to create a new version instead of editing in-place
-
-#### Scenario ZTC-01c: Publish a draft case type
-
-- GIVEN a draft case type "Bezwaarschrift" with at least one status type configured
-- WHEN the admin clicks "Publiceren"
-- THEN the system MUST validate that the case type has: at least one status type, at least one status marked as `isFinal`, a valid `processingDeadline`
-- AND if validation passes, the case type `isDraft` MUST be set to `false`
-- AND the case type MUST become available for case creation
-
-#### Scenario ZTC-01d: Publish validation fails
-
-- GIVEN a draft case type "Nieuwe Procedure" with no status types configured
-- WHEN the admin clicks "Publiceren"
-- THEN the system MUST reject the publish with error: "Zaaktype kan niet gepubliceerd worden: geen statustypen geconfigureerd"
-- AND the case type MUST remain in draft status
-
-#### Scenario ZTC-01e: Delete draft case type
-
-- GIVEN a draft case type "Test Zaaktype" with no active cases
-- WHEN the admin clicks "Verwijderen"
-- THEN the system MUST display a confirmation dialog
-- AND upon confirmation, the case type and all linked status types, document types, property definitions, and result types MUST be deleted
-
-#### Scenario ZTC-01f: Delete published case type with active cases blocked
-
-- GIVEN a published case type "Omgevingsvergunning" with 10 active cases
-- WHEN the admin attempts to delete it
-- THEN the system MUST reject: "Kan niet verwijderd worden: er zijn 10 actieve zaken van dit type"
-- AND the case type MUST NOT be deleted
-
-#### Scenario ZTC-01g: Set case type as default
-
-- GIVEN multiple published case types
-- WHEN the admin marks "Omgevingsvergunning" as the default
-- THEN the case creation form MUST pre-select this case type
-- AND only one case type MAY be the default at a time
-
----
-
-### REQ-ZTC-02: Status Diagram Editor
-
-The system MUST provide a visual editor for configuring the status lifecycle of a case type.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-02a: Add and order statuses
-
-- GIVEN a new case type "Bezwaarschrift"
-- WHEN the admin opens the status configuration tab
-- THEN the admin MUST be able to add status types: "Ontvangen", "Vooronderzoek", "Hoorzitting", "Beslissing op bezwaar"
-- AND statuses MUST be orderable via drag-and-drop or order number input
-- AND the admin MUST mark "Beslissing op bezwaar" as `isFinal = true`
-- AND a visual diagram MUST show the status flow as a horizontal timeline
-
-#### Scenario ZTC-02b: Configure status properties
-
-- GIVEN status type "Hoorzitting" on case type "Bezwaarschrift"
-- WHEN the admin edits the status
-- THEN the admin MUST be able to configure: description, `notifyInitiator` (yes/no), `notificationText`, required properties at this status, required documents at this status
-
-#### Scenario ZTC-02c: Prevent deleting status with active cases
-
-- GIVEN a status type "In behandeling" that is the current status of 5 cases
-- WHEN the admin attempts to delete this status
-- THEN the system MUST reject: "Kan niet verwijderd worden: 5 zaken hebben deze status"
-
-#### Scenario ZTC-02d: Reorder statuses
-
-- GIVEN a case type with statuses in order: "Ontvangen" (1), "In behandeling" (2), "Besluitvorming" (3), "Afgehandeld" (4)
-- WHEN the admin drags "Besluitvorming" before "In behandeling"
-- THEN the order MUST update to: "Ontvangen" (1), "Besluitvorming" (2), "In behandeling" (3), "Afgehandeld" (4)
-- AND the visual timeline MUST reflect the new order immediately
-
-#### Scenario ZTC-02e: Status diagram color coding
-
-- GIVEN the visual status diagram
-- THEN each status type MUST display with a color indicator
-- AND the admin SHOULD be able to assign a color to each status (or use system defaults)
-- AND the colors MUST be used consistently in the case list and case detail views
-
----
-
-### REQ-ZTC-03: Document Type Configuration
-
-The system MUST provide a UI for configuring which document types are required per case type.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-03a: Add required document types
-
-- GIVEN case type "Omgevingsvergunning"
-- WHEN the admin opens the document configuration tab
-- THEN the admin MUST be able to add document types with: name, direction (incoming/outgoing/internal), requiredAtStatus (dropdown of configured statuses), description
-- AND example: "Bouwtekening" (incoming, required at "In behandeling")
-
-#### Scenario ZTC-03b: Edit document type
-
-- GIVEN a document type "Bouwtekening" configured as incoming, required at "In behandeling"
-- WHEN the admin changes requiredAtStatus to "Ontvangen"
-- THEN the system MUST update the document type configuration
-- AND the change MUST affect new cases only (existing cases retain their current checklist state)
-
-#### Scenario ZTC-03c: Delete document type
-
-- GIVEN a document type "Welstandsadvies" on case type "Omgevingsvergunning"
-- WHEN the admin deletes it
-- THEN the document type MUST be removed from the case type configuration
-- AND existing cases MUST NOT lose already-uploaded documents of this type
-
-#### Scenario ZTC-03d: Document type direction validation
-
-- GIVEN the admin adding a new document type
-- THEN the direction dropdown MUST only show: "incoming" (van aanvrager), "outgoing" (naar aanvrager), "internal" (intern)
-- AND each direction MUST have a localized label in Dutch and English
-
----
-
-### REQ-ZTC-04: Property Definition Configuration
-
-The system MUST provide a UI for configuring custom property definitions (case-specific data fields) per case type.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-04a: Add custom properties
-
-- GIVEN case type "Omgevingsvergunning"
-- WHEN the admin opens the property definitions tab
-- THEN the admin MUST be able to add properties with: name, type (text/number/date/boolean/enum/reference), required (yes/no), requiredAtStatus, description, validation rules
-- AND example: "Bouwkosten" (number, required at "Ontvangen", min=0)
-
-#### Scenario ZTC-04b: Enum property with predefined values
-
-- GIVEN a property "Type bouwwerk" on case type "Omgevingsvergunning"
-- WHEN the admin sets type to "enum"
-- THEN the admin MUST be able to define allowed values: ["Woning", "Bedrijfspand", "Bijgebouw", "Overig"]
-- AND the case form MUST render a dropdown with these options
-
-#### Scenario ZTC-04c: Property with validation rules
-
-- GIVEN a property "Bouwkosten" of type "number"
-- WHEN the admin configures validation: min=0, max=10000000
-- THEN the case form MUST enforce these limits
-- AND the admin UI MUST display the configured validation rules clearly
-
-#### Scenario ZTC-04d: Required-at-status property
-
-- GIVEN a property "Kadastraal nummer" with requiredAtStatus = "In behandeling"
-- WHEN the configuration is saved
-- THEN cases of this type MUST NOT be able to advance to "In behandeling" without this property filled
-- AND the case detail MUST visually indicate which properties are required at the next status
-
-#### Scenario ZTC-04e: Reference property to external register
-
-- GIVEN a property "BAG adres" of type "reference"
-- WHEN the admin configures it to reference the BAG register's nummeraanduiding schema
-- THEN the case form MUST show a search field for looking up BAG addresses
-- AND the selected address MUST be stored as a reference to the BAG object
-
----
-
-### REQ-ZTC-05: Role Type Configuration
-
-The system MUST provide a UI for configuring which role types are available per case type.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-05a: Add role types
-
-- GIVEN case type "Omgevingsvergunning"
-- WHEN the admin opens the role types tab
-- THEN the admin MUST be able to add role types with: name, description, maxCount (e.g., 1 for handler, unlimited for advisors)
-- AND example: "Behandelaar" (max 1), "Aanvrager" (max 1), "Technisch adviseur" (unlimited)
-
-#### Scenario ZTC-05b: Role type with person/organization restriction
-
-- GIVEN a role type "Aanvrager"
-- WHEN the admin configures it
-- THEN the admin MUST be able to set whether the role can be filled by: person only, organization only, or both
-- AND this restriction MUST be enforced when adding participants to a case
-
-#### Scenario ZTC-05c: Default role types pre-populated
-
-- GIVEN a new case type is created
-- THEN the system SHOULD pre-populate with default role types: "Behandelaar" and "Aanvrager"
-- AND the admin MAY add, edit, or remove these defaults
-
----
-
-### REQ-ZTC-06: Result Type Configuration
-
-The system MUST provide a UI for configuring which result types are available per case type, including archival rules.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-06a: Add result types with archival rules
-
-- GIVEN case type "Omgevingsvergunning"
-- WHEN the admin opens the result types tab
-- THEN the admin MUST be able to add result types with: name, description, archiveAction (retain/destroy), retentionPeriod (ISO 8601 duration), retentionDateSource (case_completed/case_started)
-- AND example: "Vergunning verleend" (retain, P20Y, case_completed)
-
-#### Scenario ZTC-06b: Result type selectielijst alignment
-
-- GIVEN the Dutch Selectielijst for archive management
-- WHEN the admin configures a result type
-- THEN the system SHOULD provide a selectielijst dropdown for common archival categories
-- AND the archiveAction and retentionPeriod SHOULD auto-fill based on the selectielijst selection
-
-#### Scenario ZTC-06c: At least one result type required for publish
-
-- GIVEN a case type with no result types configured
-- WHEN the admin attempts to publish the case type
-- THEN the system MUST warn: "Geen resultaattypen geconfigureerd. Zaken kunnen niet worden afgesloten zonder resultaat."
-- AND the admin MAY proceed (result is optional at some case types) or add result types first
-
----
-
-### REQ-ZTC-07: Parafeerroute Configuration
-
-The system MUST provide a UI for configuring B&W parafeerroutes per case type and voorstel type.
-
-**Feature tier**: V2
-
-
-#### Scenario ZTC-07a: Configure parafeerroute
-
-- GIVEN case type "Omgevingsvergunning"
-- WHEN the admin opens the parafeerroute configuration
-- THEN the admin MUST be able to define ordered steps: step name, actor type (role/person/group), action (advise/parafeer/accord), parallel (yes/no)
-- AND the route MUST be previewable as a visual flow diagram
-
-#### Scenario ZTC-07b: Parafeerroute with parallel steps
-
-- GIVEN a parafeerroute with 4 steps
-- WHEN the admin marks steps 2 and 3 as parallel
-- THEN the visual diagram MUST show steps 2 and 3 side by side
-- AND both MUST be completed before step 4 can start
-
-#### Scenario ZTC-07c: Parafeerroute template reuse
-
-- GIVEN a parafeerroute configured on "Omgevingsvergunning"
-- WHEN the admin creates a new case type "Sloopvergunning"
-- THEN the admin SHOULD be able to copy the parafeerroute from "Omgevingsvergunning"
-- AND the copied route MUST be independently editable
-
----
-
-### REQ-ZTC-08: Import and Export Configuration
-
-The system MUST support importing and exporting case type configurations for sharing between environments or municipalities.
-
-**Feature tier**: V2
-
-
-#### Scenario ZTC-08a: Export case type as JSON
-
-- GIVEN a fully configured case type "Omgevingsvergunning" with 4 statuses, 5 document types, 8 properties, 4 role types, 3 result types, and a parafeerroute
-- WHEN the admin clicks "Exporteren"
-- THEN the system MUST generate a JSON file containing the complete configuration
-- AND the export MUST include all related entities (statuses, document types, properties, role types, result types, parafeerroute)
-- AND the export format MUST follow the OpenRegister JSON format with `@self` references
-
-#### Scenario ZTC-08b: Import case type from JSON
-
-- GIVEN a JSON export from another Procest instance
-- WHEN the admin clicks "Importeren" and uploads the file
-- THEN the system MUST create the case type and all related entities in draft status
-- AND the admin MUST review and publish before the type becomes active
-- AND conflicts (e.g., duplicate names) MUST be flagged for resolution
-
-#### Scenario ZTC-08c: ZTC catalog sync
-
-- GIVEN a ZGW Catalogi API endpoint with zaaktypen
-- WHEN the admin configures sync with the external catalog
-- THEN the system MUST import zaaktypen, statustypen, resultaattypen, and informatieobjecttypen
-- AND the imported types MUST be mapped to Procest's internal model
-
-#### Scenario ZTC-08d: Export preserves relationships
-
-- GIVEN a case type with status type "In behandeling" referenced by a property definition (requiredAtStatus)
-- WHEN the case type is exported
-- THEN the export MUST preserve the relationship between property definition and status type
-- AND upon import, the relationship MUST be correctly re-established
-
----
-
-### REQ-ZTC-09: Version Management
-
-The system SHALL support versioning of case type configurations.
-
-**Feature tier**: V2
-
-
-#### Scenario ZTC-09a: Create new version
-
-- GIVEN a published case type "Omgevingsvergunning v1" with 50 active cases
-- AND a new regulation requires changes to the status flow
-- WHEN the admin clicks "Nieuwe versie aanmaken"
-- THEN the system MUST clone the current configuration as "Omgevingsvergunning v2" in draft
-- AND existing cases MUST remain linked to v1
-- AND new cases MUST use v2 once published
-- AND both versions MUST be visible in the admin overview
-
-#### Scenario ZTC-09b: Version comparison
-
-- GIVEN two versions of "Omgevingsvergunning" (v1 and v2)
-- WHEN the admin views the version history
-- THEN the system SHOULD show a diff of changes between versions
-- AND the diff MUST highlight: added statuses, removed statuses, changed properties, changed deadlines
-
-#### Scenario ZTC-09c: Retire old version
-
-- GIVEN "Omgevingsvergunning v1" with 3 remaining active cases (47 completed)
-- WHEN the admin retires v1
-- THEN the 3 active cases MUST remain on v1 until completion
-- AND no new cases can be created with v1
-- AND the admin overview MUST mark v1 as "retired"
-
----
-
-### REQ-ZTC-10: Test Mode
-
-The system SHALL support testing a case type configuration before publishing.
-
-**Feature tier**: V2
-
-
-#### Scenario ZTC-10a: Test case type in sandbox
-
-- GIVEN a draft case type "Nieuwe Subsidie"
-- WHEN the admin clicks "Testen"
-- THEN the system MUST allow creating a test case that does not appear in production views
-- AND the admin MUST be able to walk through the full lifecycle: status changes, document uploads, property filling
-- AND the test case MUST be automatically cleaned up after testing
-
-#### Scenario ZTC-10b: Test mode visual indicator
-
-- GIVEN a test case created from a draft case type
-- WHEN the admin views the test case
-- THEN the case MUST display a prominent "TEST" banner
-- AND the case MUST NOT appear in dashboards, reports, or the main case list
-
-#### Scenario ZTC-10c: Test mode limitations
-
-- GIVEN a test case
-- THEN the system MUST NOT send real notifications (initiator notifications, assignment notifications)
-- AND the test case MUST NOT be counted in KPIs or SLA metrics
-- AND the test case MUST be deletable without audit trail requirements
-
----
-
-### REQ-ZTC-11: Admin Settings Navigation
-
-The admin UI MUST provide clear navigation between case type configuration areas.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-11a: Tab-based configuration
-
-- GIVEN an admin editing case type "Omgevingsvergunning"
-- THEN the configuration screen MUST show tabs: General, Statuses, Document Types, Properties, Role Types, Result Types
-- AND each tab MUST show the count of configured items (e.g., "Statuses (4)")
-- AND switching tabs MUST preserve unsaved changes or prompt to save
-
-#### Scenario ZTC-11b: Case type list overview
-
-- GIVEN 5 case types: 3 published, 2 draft
-- WHEN the admin navigates to Procest Admin > Zaaktypen
-- THEN the list MUST show: title, status (published/draft badge), processing deadline, validity period, active case count, and actions (edit, delete, set default)
-- AND published case types MUST be visually distinct from drafts
-
-#### Scenario ZTC-11c: Inline validation feedback
-
-- GIVEN the admin is configuring a case type
-- WHEN the admin leaves a required field empty (e.g., title)
-- THEN the system MUST show inline validation errors immediately
-- AND the "Save" button MUST be disabled while validation errors exist
-
----
-
-### REQ-ZTC-12: Duration Picker for Processing Deadline
-
-The system MUST provide a user-friendly duration picker for the ISO 8601 processing deadline.
-
-**Feature tier**: V1
-
-
-#### Scenario ZTC-12a: Duration picker input
-
-- GIVEN the admin is setting `processingDeadline` on a case type
-- THEN the system MUST provide a picker with fields for: weeks and/or days
-- AND the picker MUST convert the input to ISO 8601 duration format (e.g., 8 weeks = "P56D")
-- AND the picker MUST display common presets: "6 weken (P42D)", "8 weken (P56D)", "13 weken (P91D)", "26 weken (P182D)"
-
-#### Scenario ZTC-12b: Custom duration entry
-
-- GIVEN the admin wants a non-standard deadline of 35 days
-- WHEN the admin enters "35 days" in the picker
-- THEN the system MUST store "P35D" as the processingDeadline
-- AND the display MUST show "35 dagen (5 weken)"
-
-#### Scenario ZTC-12c: Duration picker for extension period
-
-- GIVEN a case type with `extensionAllowed = true`
-- THEN the admin MUST be able to set `extensionPeriod` using the same duration picker
-- AND common presets for extensions SHOULD be: "2 weken (P14D)", "4 weken (P28D)", "6 weken (P42D)"
-
-## Dependencies
-
-- **Case Types spec** (`../case-types/spec.md`): Defines the data model this UI configures.
-- **Case Management spec** (`../case-management/spec.md`): Cases use the configured case types.
-- **B&W Parafering spec** (`../bw-parafering/spec.md`): Parafeerroutes are configured per case type.
-- **Admin Settings spec** (`../admin-settings/spec.md`): Admin UI framework and navigation.
-- **OpenRegister**: All configuration stored as OpenRegister objects.
-
----
-
-### Current Implementation Status
-
-**V1 partially implemented.** Basic case type CRUD and status configuration exist. Advanced features (document type config, property definition config, role type config, result type config, parafeerroute, import/export, versioning, test mode) are not implemented.
-
-**Implemented (with file paths):**
-- **Case type CRUD via admin UI (REQ-ZTC-01)**:
- - `src/views/settings/CaseTypeList.vue` -- lists all case types with title, draft/published badge, processing deadline, validity period, and actions (set default, edit, delete).
- - `src/views/settings/CaseTypeDetail.vue` -- detail/edit view for a single case type with tabs.
- - `src/views/settings/CaseTypeAdmin.vue` -- admin wrapper component.
- - `src/views/settings/AdminRoot.vue` -- admin root with case type list and detail.
- - `src/views/settings/tabs/GeneralTab.vue` -- general properties tab for case type editing (title, description, processingDeadline, confidentiality, etc.).
-- **Status diagram editor (REQ-ZTC-02 partial)**:
- - `src/views/settings/tabs/StatusesTab.vue` -- status type configuration within a case type. Supports adding, ordering, and editing statuses. Includes `isFinal` marking.
- - `src/views/cases/components/StatusTimeline.vue` -- visual timeline showing status progression on case detail.
-- **Case type validation**: `src/utils/caseTypeValidation.js` -- client-side validation for case type fields.
-- **Navigation**: `src/navigation/MainMenu.vue` -- "Case Types" menu item in the settings footer, linked to `/case-types` route.
-- **Router**: `src/router/index.js` -- route `{ path: '/case-types', name: 'CaseTypes', component: AdminRoot }`.
-- **Schema definitions**: All 7 configuration schemas defined in `lib/Settings/procest_register.json`: `caseType`, `statusType`, `resultType`, `roleType`, `propertyDefinition`, `documentType`, `decisionType`.
-- **ZGW catalog API**: `lib/Controller/ZtcController.php` -- full ZGW Catalogi API with CRUD for zaaktypen, statustypen, resultaattypen, informatieobjecttypen. Includes publish endpoints (`POST .../zaaktypen/{uuid}/publish`).
-- **ZGW catalog rules**: `lib/Service/ZgwZtcRulesService.php` -- validation rules for zaaktype creation and modification.
-
-**Not yet implemented:**
-- **REQ-ZTC-03: Document type configuration (V1)**: No admin UI for configuring document types per case type. The `documentType` schema exists but no management UI.
-- **REQ-ZTC-04: Property definition configuration (V1)**: No admin UI for configuring custom properties per case type. The `propertyDefinition` schema exists but no management UI. No enum value editor.
-- **REQ-ZTC-05: Role type configuration (V1)**: No admin UI for configuring role types per case type. The `roleType` schema exists but no management UI.
-- **REQ-ZTC-06: Result type configuration (V1)**: No admin UI for configuring result types per case type. The `resultType` schema exists but no management UI.
-- **REQ-ZTC-07: Parafeerroute configuration (V2)**: No parafeerroute configuration UI. No visual flow diagram for approval routes.
-- **REQ-ZTC-08: Import/export configuration (V2)**: No JSON export/import for case type configurations. No ZTC catalog sync.
-- **REQ-ZTC-09: Version management (V2)**: No versioning of case type configurations. No clone/new version functionality.
-- **REQ-ZTC-10: Test mode (V2)**: No sandbox/test mode for case types.
-- **REQ-ZTC-12: Duration picker (V1)**: No duration picker component; processingDeadline is entered as raw ISO 8601 string.
-- **Status drag-and-drop ordering**: Status ordering may be manual (number input) rather than drag-and-drop.
-- **Warning on editing published case type**: No "10 active cases" warning when editing a published case type.
-- **Publish validation**: No pre-publish validation checking for status types, final status, and processing deadline.
-
-### Standards & References
-
-- **ZGW Catalogi API (VNG Realisatie)**: Full ZGW-compliant catalog API via `ZtcController.php`. Supports ZaakType, StatusType, ResultaatType, RolType, InformatieObjectType, BesluitType, Eigenschap. Includes publish endpoints.
-- **CMMN 1.1**: CaseDefinition patterns for case type configuration with status lifecycle.
-- **GEMMA**: Zaaktype catalogus is a standard component in the GEMMA reference architecture.
-- **Common Ground**: Configuration data stored as OpenRegister objects in the information layer.
-- **Selectielijst**: Dutch archival selection list determining retention periods per case type category.
-- **Archiefwet**: Archival rules linked to result types (retain/destroy with retention periods).
-- **Competitor reference**: Dimpact ZAC provides a zaaktype admin interface integrated with Flowable CMMN modeling. CaseFabric offers a visual case type designer with drag-and-drop status flows. Flowable Design provides low-code case definition with visual CMMN editor.