Release: merge development into beta#18
Open
github-actions[bot] wants to merge 583 commits into
Open
Conversation
This was referenced Apr 18, 2026
…anifest, keep calc service (#373) Aligns the legesberekening surface with the manifest renderer pattern established by procest-manifest-v1 (#320) and mirrored by the parafeerroute refactor: tariff and history CRUD is now declared in src/manifest.json against three OpenRegister schemas, while the domain logic in LegesCalculationService / LegesExportService and the non-CRUD endpoints (/api/leges/calculate, /recalculate, /verrekening, /teruggaaf, /export) remain untouched. Schemas (lib/Settings/procest_register.json) - legesverordening: municipal fee regulation (year, effectiveDate, status, isActive, globalMaximum, reference) - legesartikel: tariff line with type vast | percentage | staffel | maximum | combinatie, grondslagField, brackets, subArtikelen, category, order, scoped by verordening - legesberekening: per-case calculation result with breakdown, version history (version, previousVersion, previousTotal, difference, correctionReason), status concept | opgelegd | verrekend | teruggegeven, exportedAt + exportFormat All three are registered on the procest register so OpenRegister exposes the standard CRUD endpoints — no bespoke controllers needed. Manifest pages (src/manifest.json) - Legesverordeningen — index of regulations, columns name/year/ effectiveDate/status/isActive - LegesverordeningDetail — detail with sidebar tabs Overview, Artikelen (related-index on legesartikel filtered by verordening, with create/edit/delete), Audit trail - Legesberekeningen — index of calculation history, columns case/ verordening/total/version/status/calculatedBy/calculatedAt, read-only actions - LegesberekeningDetail — detail with Overview and Audit trail - Two new settings-section menu entries (Legesverordeningen at order 96, Legesberekeningen at order 97) Preserved - LegesController::calculate, recalculate, verrekening, teruggaaf, export — all five remain (domain endpoints, no CRUD proxying) - LegesCalculationService (vast / percentage / staffel / maximum / combinatie rules + verrekening + teruggaaf) — untouched - LegesExportService (CSV / XML / JSON export to financial system) — untouched - /api/leges/* routes in appinfo/routes.php — all five kept Deletions - None required. The pre-existing controller already exposes only domain endpoints; no CRUD methods, routes, or Vue views for leges tariffs / history existed prior to this change. Validation - jq parses both JSON files; new schemas + register list verified - node tests/validate-manifest.js — structural lint PASS, 21 pages - php -l on LegesController, LegesCalculationService, LegesExportService, appinfo/routes.php — no syntax errors
… manifest, keep workflow + reminder (#374) Move adviesAanvraag CRUD (index/show/create/update/destroy) off the bespoke AdviceController onto the manifest renderer (OpenRegister objects endpoint). The controller now exposes only the two operations the renderer cannot replicate: status transitions (which trigger the adviseur / requester notifications) and manual reminder dispatch. Deletions - AdviceController: index, create, show, update, destroy (CRUD proxies) - AdviceService: createAdvice, receiveAdvice (replaced by transitionStatus) - routes.php: advice#index/create/show/update/destroy - adviceApi.js: getAdvice, updateAdvice, deleteAdvice, createAdvice (replaced by createAdviceWithNotification chaining OR + transition) Kept (workflow / cron / detail panel) - AdviceController::transitionStatus, dispatchReminder - AdviceService::transitionStatus, dispatchReminder, applyWorkflowGuard, getOpenAdvice, expireAdvice, getAdviceForCase - BackgroundJob/AdviceDeadlineJob (entirely — now calls dispatchReminder) - src/views/cases/components/AdviesPanel.vue (case-detail sidebar tab) - src/views/cases/components/AdviesAanvraagDialog.vue (workflow trigger) New manifest pages - index: /advice (admin view across all advice requests) - detail: /advice/:id (requester / advisor view) - CaseDetail.sidebarTabs: new "advies" tab rendered via AdviesPanel Schema adviesAanvraag (lib/Settings/procest_register.json) unchanged.
… manifest, keep middleware + provision (#375) Generic tenant CRUD (list / create / update / delete) no longer needs a bespoke controller, service wrapper, Vue list page, or REST routes — the shared manifest renderer drives those declaratively against the tenant schema in procest_register.json. Domain-specific logic stays in PHP. Deletions - lib/Controller/TenantController::index() / ::create() (proxy CRUD) - lib/Service/TenantService::createTenant() and its slugify() helper - appinfo/routes.php — tenant#index, tenant#create - src/services/tenantApi.js (whole file — manifest pages talk to OR) - src/views/settings/tabs/TenantSettingsTab.vue (orphan; replaced by manifest index page) - src/views/cases/components/TenantSwitcher.vue (orphan; never mounted) Preserved (deliberate) - lib/Middleware/TenantMiddleware — cross-cutting tenant scoping; unchanged, still in DI and still exempts TenantController. - lib/Controller/TenantController::provision() / ::usage() / ::current() — provisioning workflow, resource-usage aggregation, and current-tenant resolution are domain logic the manifest cannot express. - lib/Service/TenantService::provisionTenant(), ::getResourceUsage(), ::getTenantForUser(), ::getTenantByGroupId(), ::isUserInTenant(), ::isPlatformAdmin() — all kept. - appinfo/routes.php — tenant#current, tenant#provision, tenant#usage. New manifest pages (src/manifest.json) - Tenants (index, /settings/tenants) — schema-driven list with row actions for provision (emit) and view-usage (navigate to detail). - TenantDetail (detail, /settings/tenants/:id) — three sidebar tabs: Overview (data + metadata), Usage (data), Provisioning history (audit-trail). Header actions for provision + refresh-usage. - Menu entry "Tenants" added to the settings section with permission: "admin" so non-admins never see the link. Admin-only guarantee - The new menu entry is gated by CnAppNav's permission filter (permission: "admin"). src/App.vue now injects "admin" into the permissions array when window.OC.isUserAdmin() is true, matching the backend TenantService::isPlatformAdmin() check. - Row / header action buttons also carry permission: "admin" so CnRowActions / CnActionsBar hide them for non-admins. - The three preserved REST endpoints (provision / usage) keep their isPlatformAdmin() check — backend authority is unchanged.
…, declare manifest pages (#376) Generic CRUD on parafeerroute objects is now served by OpenRegister's auto-exposed /api/objects/<register>/<schema> endpoints and rendered by the @conduction/nextcloud-vue manifest engine (CnIndexPage + CnDetailPage). The bespoke ParafeerRouteController/Service/Vue CRUD layer is removed. Workflow engine logic (start, complete-step, skip-step, add-step) is preserved as legitimate domain endpoints on /api/parafeer-route/voorstel/*. Deleted: - ParafeerRouteController::{index, show, create, update, destroy} - ParafeerRouteService::{listRoutes, getRoute, createRoute, updateRoute, deleteRoute, clearExistingDefault, bootstrapRoute} - src/views/settings/components/ParafeerRoutesTab.vue - src/views/settings/components/ParafeerRouteDialog.vue - src/views/settings/ParafeerRouteAdmin.vue (already orphaned) - parafeerRouteApi.js CRUD wrappers (listRoutes, getRoute, createRoute, updateRoute, deleteRoute) - 5 CRUD route entries from appinfo/routes.php - ParafeerRoutesTab section in AdminRoot.vue Added: - Manifest page id=Parafeerroutes (type=index, /settings/parafeerroutes) - Manifest page id=ParafeerrouteDetail (type=detail, /settings/parafeerroutes/:id) - Menu entry id=ParafeerroutesMenu under settings section Schema (lib/Settings/procest_register.json#parafeerroute) was already complete (name, voorstelType, caseType, steps[], isDefault, description) — no schema changes required. Kept as domain logic (engine, not CRUD): - ParafeerRouteController::{start, completeStep, skipStep, addStep} - ParafeerRouteService::{startParafering, completeStep, skipStep, addAdhocStep, advanceVoorstel, activateStep, normalizeSteps, appendAuditTrail, bootstrapVoorstel, requireConfig, requireUserId, toArray} - src/views/settings/components/ParafeerStapEditor.vue (still useful as step-editor; not currently re-mounted under detail page — flagged for human review whether to expose it via customComponents slot) - src/utils/parafeerEngine.js helpers (getNextStep, isActiveActor, etc.) - AddStepDialog.vue, SkipStepDialog.vue (call engine endpoints)
…ia manifest, keep transfer + public-token logic (#377) The case-sharing feature owned controller + service methods that were just thin proxies onto OpenRegister CRUD. Those move to the manifest renderer; the share-token cryptography, audit logging, transfer workflow and unauthenticated public-token endpoints stay in app code. Deleted (pure OR proxies): - CaseSharingController::listShares (GET /api/shares/{caseId}) - CaseSharingController::modifyShare (PUT /api/shares/{shareId}) - CaseSharingController::listPartners (GET /api/partners) - CaseSharingController::createPartner (POST /api/partners) - CaseSharingController::updatePartner (PUT /api/partners/{partnerId}) - CaseSharingService::getSharesByCase - CaseSharingService::modifyShare - CaseSharingService::listPartners - CaseSharingService::createPartner (groupId derivation should be a schema default or pre-save hook) - CaseSharingService::updatePartner Kept (domain logic, not CRUD): - CaseSharingController::createShare — generates 128-bit token, applies default field exclusions, writes audit log - CaseSharingController::revokeShare — writes audit log + tracks revokedBy + revokedAt (soft delete with attribution) - CaseSharingController::initiateTransfer / handleTransfer — workflow endpoints around CaseTransferService accept/reject - PublicShareController (all four methods) — unauthenticated token- based access with password lockout - CaseSharingService: generateToken, createTokenShare, createPartnerShare, revokeShare, validateToken, getFilteredCaseData, maskBsn, recordFailedAttempt, resetFailedAttempts, storeExternalDocument Added schemas to lib/Settings/procest_register.json (caseShare, partnerOrganization, casetransfer) so the manifest renderer can serve them via OpenRegister. SettingsService slug mapping aligned to the 'casetransfer' slug. Added manifest pages: - CaseShares (index) /cases/:caseId/shares schema=caseShare - Partners (index) /settings/partners schema=partnerOrganization - PartnerDetail (detail) /settings/partners/:id - Transfers (index) /transfers schema=casetransfer - TransferDetail (detail) /transfers/:id Menu entries added: Transfers (main, order 80), Partner organisations (settings, order 97). Security guarantees preserved: token generation via random_bytes(16), bcrypt password hashing, 5-attempt 15-minute lockout, field-level exclusions, BSN masking, public endpoints require valid token + remain CSRF-exempt.
Add OpenSpec change proposal for a reusable MapComponent.vue that wraps CnMapWidget and serves as the shared map surface for case detail map tab, dashboard map widget, and public case page. Includes proposal, design, tasks (T01-T05 + V01-V04), and REQ-MC-1..6 delta spec covering the wrapper, prop API, events, interactive mode, NL Design System / accessibility, and customComponents registration. Sister spec to case-location and case-map-overview.
Centralised PDOK integration layer for Procest covering Locatieserver (suggest/free/lookup/reverse), BAG (nummeraanduiding/verblijfsobject/pand), Kadaster (perceel), default basemap config, admin settings, shared APCu cache, rate limiting, and outage degraded-mode handling. Consolidates the private PdokLocatieserverClient from case-location into a shared service so case-location, case-map-overview, and map-component all consume the same integration surface. Spec only — no code changes.
Add openspec change wms-wfs-layers — config-driven WMS/WFS overlay extension to map-component. Introduces wmsLayer schema, manifest-driven admin index page, per-case-type layer subscriptions (caseType.layerIds), WmsWfsService client (proxy-only), CaseMap layerIds prop, legend renderer with toggle/opacity, queryable-aware popups, and WFS extent guard. Spec only — no code changes. REQ-WMS-1..8 with Given/When/Then scenarios. Strict validation passes.
Manifest-first apply of openspec change `process-step-configuration`.
Schema:
- Bump workflowTemplate version 1.0.0 → 1.1.0 with additive
documentation of the per-step `config` sub-object
({sla, requiredFields, autoActions, escalationRule}).
No new top-level register schema.
Validator:
- Add pure-function StepConfigValidator (no DI, no I/O) implementing
the seven rules from design.md. Returns structured {path, code,
message} errors; never raw exception text.
Editor:
- Extend the existing StepConfigPanel.vue with a collapsible
"Geavanceerd" panel for sla / requiredFields / escalationRule.
- Pass `read-only` from WorkflowEditor.vue via a new `isPublished`
computed (template.isDraft === false), per REQ-PSC-7-002.
Deferred (host services not yet on development):
- T03 publish hook, T04/T05 status-transition-engine consumption,
T06 task creator, T09 bezwaar backfill, T10 spec flip.
Expose the existing workflowTemplate schema with a draft → published →
deprecated lifecycle, version pinning, a consumer contract for
status-transition-engine + role-based-step-routing, and a backfill
repair step — without growing bespoke CRUD.
Schema (manifest-first; pure CRUD goes through the manifest renderer +
OpenRegister auto-routing):
- lib/Settings/procest_register.json — workflowTemplate gains a
lifecycleStatus enum (draft|published|deprecated), version bumped to
1.1.0; caseType gains a workflowDefinition uuid reference for the
pinned active version.
- lib/Service/SettingsService.php — workflow_definition_schema alias
mirrors workflow_template_schema so consumer specs can resolve the
schema id without depending on the legacy slug.
Manifest pages (no bespoke Vue components):
- WorkflowDefinitions (index) at /settings/workflow-definitions
- WorkflowDefinitionDetail (detail) with overview / steps /
transitions / audit-trail sidebar tabs and publish / deprecate /
clone actions wired to the controller endpoints.
- Settings menu entry "Workflow definitions".
Backend services (legitimate domain logic, not CRUD):
- WorkflowDefinitionService — lifecycle (publish, deprecate,
cloneDefinition) and read-only consumer contract
(getActiveDefinitionFor, getDefinition, getDefinitionForCase,
listVersions). Atomic publish deprecates the previously active
version and pins caseType.workflowDefinition; deprecate refuses to
strand open cases; clone increments version. Referential integrity
check rejects publishing a draft whose transitions reference foreign
statusType ids. All errors logged + static error strings returned —
no raw exception leakage.
- WorkflowDefinitionController — five action-only endpoints (publish,
deprecate, clone, active, for-case). No CRUD methods.
Routes (action endpoints only, /api/workflow-definitions/...):
- POST /{id}/publish, /{id}/deprecate, /{id}/clone
- GET /active/{caseTypeId}, /for-case/{caseId}
Migration:
- lib/Repair/MigrateWorkflowDefinitions.php — idempotent backfill that
synthesises one published workflowTemplate per existing caseType from
its statusType ordering, pins caseType.workflowDefinition, and binds
open cases to workflowVersion 1. Skips caseTypes that already have a
workflowDefinition reference or any existing template.
- appinfo/info.xml — repair step registered.
Implements openspec change workflow-definition-model.
Add the parafering-audit-trail change covering: - paraferingAuditEntry schema (REQ-PAT-1) - Event-sourced audit writes via ParafeerTransitionEvent (REQ-PAT-2) - Append-only validator (REQ-PAT-3) - Reuse of OpenRegister audit-trail-immutable (REQ-PAT-4) - Content snapshot at transition moment (REQ-PAT-5) - Archive export endpoint with MDTO 1.0 envelope (REQ-PAT-6) - Manifest index page for auditor browsing (REQ-PAT-7) - Retention policy aligned with Archiefwet — 20y decisions / 7y other (REQ-PAT-8) Sister to parafeerroute-engine, parafering-actions, voorstel-management. Strict validation passes: 'openspec validate --type change --strict' OK. No code.
Implements the drag-and-drop visual editor for workflow templates as defined in openspec/changes/visual-workflow-editor. Frontend-only: the data layer flows through the existing workflow Pinia store (createObjectStore on workflowTemplate), so no PHP / backend changes. What this adds: - @vue-flow/core, @vue-flow/background, @vue-flow/controls dependencies - src/components/workflow/VisualWorkflowEditor.vue — main editor - src/components/workflow/NodePalette.vue — draggable node templates - src/components/workflow/WorkflowCanvas.vue — vue-flow wrapper - src/components/workflow/NodeProperties.vue — selected-node panel - src/components/workflow/EdgeProperties.vue — selected-edge panel - src/components/workflow/validator.js — live graph validator (NO_FINAL_STATUS, UNREACHABLE_FINAL, ORPHAN_NODE, DANGLING_EDGE, DUPLICATE_TRANSITION, MISSING_LABEL, CYCLE_NO_EXIT) - Manifest custom page WorkflowTemplateEditor at /settings/workflow-templates/:id/edit (the ONE acceptable custom page for this spec) - Registration of VisualWorkflowEditor in src/customComponents.js - JSON import/export round-trip through the workflowTemplate schema - openspec/changes/visual-workflow-editor/builds/build.json artifact
Introduce a reusable role-routing engine (RoleResolverService + StrategyRegistry +
5 built-in strategies) that any workflow step or status transition can use to
compute its assignee set. Migrate ParafeerRouteService.activateStep to consume
the resolver for role-typed actors so parafering inherits delegation + workload
features. Add POST /api/cases/{id}/reroute as an action endpoint for manual
recompute, plus a role-mutation listener that invalidates the APCu cache layer.
Schema: role gains delegate/delegateFrom/delegateUntil; workflowTemplate steps +
transitions document the optional routingRule object (legacy assigneeRole and
allowedRoles are normalised on read).
Manifest-first declarative automatic-action registry: CRUD on action
definitions is fully handled by OpenRegister manifest pages
(/settings/automatic-actions index + detail); the dispatch engine ships
as PHP services in lib/Service/Actions and lib/Service/Transitions.
- Add automaticAction schema (slug, type, tenantId, title, config,
version, isPublished, active) to procest_register.json; bump
register version 0.6.0 -> 0.7.0.
- Register automatic_action_schema config key + automaticAction slug
mapping in SettingsService.
- Add AutomaticActions (type:index) and AutomaticActionDetail
(type:detail) manifest pages, plus AutomaticActionsMenu under
settings.
- ActionRegistry: per-tenant, per-request cached slug lookup; rejects
unpublished, unknown, and cross-tenant references; logs each miss
via PSR-3 with the requested tenant + owner tenant.
- SideEffectDispatcher: resolves {ref:<slug>} entries via the
registry and keeps inline action JSON working for backward compat;
records one audit entry per action (type, ref, ok, error, data)
on statusRecord.dispatchedActions; never aborts on handler failure.
- Six ActionHandlerInterface implementations: SendEmail,
CreateDocument, NotifyRole, CallWebhook, MergeTemplate,
ScheduleReminder. All honour $transitionContext['dryRun'] (compute
the projected effect, return it in ActionResult.data, do NOT
mutate live state). All catch \\Throwable, log via LoggerInterface,
and return ActionResult::failure with a static error code -
never $e->getMessage() in ActionResult.error.
- Shared HandlesTemplates trait provides deterministic
{{case.path}} template rendering used by every handler so dry-run
and live output match exactly.
Apply the status-transition-engine change: introduce the engine that turns
workflowTemplate.transitions[] into deterministic case state changes — the
single write path for case.status across Procest.
Engine core (PHP services, non-CRUD per manifest-first):
- StatusTransitionService: getAvailableTransitions, execute (with guard
re-evaluation), executeFreeForm (admin-only), replay
- WorkflowTemplateLoader: per-request cached active-template lookup
- GuardRegistry + 4 built-in guards (checklist, requiredField,
requiredDocument, roleGuard); roleGuard hides silently via details.silent
- ActionHandlerRegistry + SideEffectDispatcher + 6 built-in handlers
(sendEmail, createTask, createSubCase, webhook, setField, notify);
failures logged with static error codes, never roll back the status change
- GuardFailedException with failed-guard snapshots
- Registries expose registerEvaluator/registerHandler hooks so downstream
specs (bezwaar-lifecycle, parafeerroute-engine) can plug in via DI
REST controller (action-style, not CRUD):
- GET /api/case/{caseId}/available-transitions
- POST /api/case/{caseId}/transition {transitionId, comment?}
- POST /api/case/{caseId}/transition-freeform {toStatusId, comment?} (admin)
- GET /api/case/{caseId}/transition-history
- Static error messages only; never returns $e->getMessage()
Schema + manifest (manifest-first):
- statusRecord v1.1.0: + transitionLabel, fromStatus, evaluatedGuards,
dispatchedActions, noWorkflowTemplate
- Manifest pages StatusRecords (admin index) + StatusRecordDetail with
guard/action audit tabs — no bespoke CRUD controller
Legacy ZGW backfill annotation (T13, partial): rulesStatussenCreate gains
a docblock pointer to the engine. Full migration of
ZrcController::handleEindstatusEffect into a SetFieldHandler is deferred to
a follow-up so the live ZGW request lifecycle stays intact.
Out of scope this PR: frontend AvailableTransitionsPanel/TransitionConfirmDialog
(T18-T20) — admin browsing is via the new manifest pages.
Add the shared MapComponent Vue wrapper around CnMapWidget plus the mapFormatters registry, and register MapComponent in customComponents so manifest pages MAY mount it by name. This is the manifest-first slice of openspec/changes/map-component (T01, T02). Consumer migrations (CaseMapTab T03, CaseMapWidget T04, PublicCaseView T05) remain owned by case-location, case-map-overview, and the public-case-page specs respectively — see builds/build.json deferred list for the trail.
Convert the existing `/map` page from a hand-written `CaseMapView.vue` custom component to a manifest-driven `type: 'map'` page consuming `CnMapPage` from @conduction/nextcloud-vue beta.30 (ADR-008). Changes: - src/manifest.json: CaseMap page entry switched to `type: 'map'` with full config block — register/schema, geometryField, filter ids, marker.formatter, clustering (with status-priority order blocked > in_progress > open > closed), bboxQuery threshold (5k pins), pdok-brt tile layer, sidebar config, and inline emptyState (nl). - src/services/mapFormatters.js: new — exports `caseMarkerFormatter`, `statusColor`, `statusIcon`. Handles Point + Polygon centroid + null geometry. Uses NL Design System color tokens (var(--color-status-*), no hardcoded hex). - src/main.js: registers the mapFormatters registry on Vue.prototype and on the App props (mirrors customComponents pattern). - src/customComponents.js: drop the CaseMapView import + registration. - src/views/CaseMapView.vue: deleted (replaced by CnMapPage). - builds/build.json: record application; verification tasks deferred per strict watchdog (lint + jq only) and 5k-pin perf benchmark follows in a separate change.
Add shared PHP services for PDOK Locatieserver and BAG so every consumer in Procest (case-location, case-map-overview, map-component) talks to a single ingress instead of carrying its own HTTP client. - PdokLocatieserverService: suggest, free, lookup, reverse. 24h cache for lookup/reverse, 5min for suggest, cache key includes filter (fq) params. Outage degradation: 3x 5xx in 60s -> degraded for 5min; suggest returns [] so LocationService can fall back to free-text. - PdokBagService: nummeraanduiding / verblijfsobject / pand. Response normalised to a stable Procest-internal shape (snake -> camel, bouwjaar int, oppervlakte int m2, gebruiksdoel array). - Both services route via an OpenConnector source slug when the matching pdok_*_source config key is non-empty; otherwise they call PDOK directly. OpenConnector remains an optional dependency. - Extend SettingsService with the 11 pdok_* config keys (endpoint URLs, source slugs, cache TTLs, rate ceiling, outage banner nl+en). Per manifest-first watchdog: no new admin Vue components, no new controllers, no new manifest pages. Admin config surface is the existing SettingsService allow-list. T03/T05/T06/T07/T08 are deferred and tracked in builds/build.json; case-location LocationService refactor will land in the case-location apply.
Apply the enforcement-lhs change manifest-first: - Add `lhsMatrix` and `lhsRecommendation` schemas to procest_register.json with the 3-D matrix (3 ernst x 4 gedrag x 4 actorType = 48 cells) and the per-enforcement recommendation row. Matrix is versioned and immutable per REQ-LHS-8; historical recommendations freeze matrixVersion. - Wire schemas into SettingsService config keys + slug map. - Register manifest pages LhsMatrices/LhsMatrixDetail (admin-only) and LhsRecommendations/LhsRecommendationDetail for audit browsing — no bespoke CRUD controller; OpenRegister serves CRUD. - Implement OCA\Procest\Service\Vth\LhsRecommendationService with recommend(ernst, gedrag, actorType, lhsVersion?) and override(recommendation, intervention, justification, userRole) — override-up is gated to manager role (REQ-LHS-5/6); identity is server-derived from IUserSession (REQ-LHS-3). - LhsController owns the engine actions POST /api/lhs/recommend and POST /api/lhs/override. - SeedLhsMatrix repair step idempotently imports the Landelijke Handhavingsstrategie 2024 (48 cells) from lib/Settings/seed/lhs-matrix-2024.json. STRICT watchdog: php -l + jq only; no composer check; no i18n. Refs openspec/changes/enforcement-lhs/tasks.md T01-T03, T05.
Seed six canonical VTH workflow templates as published workflowTemplate v1 objects via WorkflowDefinitionService::createDraft() + publish(). - Add lib/Settings/seed/vth-workflow-templates/ catalog with: - aanvraag-omgevingsvergunning (Awb 4:13, 8 weken) - toezichtbezoek (spawn-link naar handhavingstraject) - handhavingstraject (Awb 5:24 redelijke termijn) - bezwaar (cross-link naar bezwaar-lifecycle, geen redefinitie) - klacht-toezicht (Awb 9:11, 6 weken) - spoedig-herstel (Awb 5:31, onverwijld) - Add lib/Repair/SeedVthWorkflowTemplates.php idempotent repair step; registered as post-migration repair step in appinfo/info.xml. - Repair step resolves caseType slug + statusType names to UUIDs at install time, generates deterministic UUID5 ids for steps/transitions, and routes every workflowTemplate mutation through the lifecycle service to respect the immutability invariant from workflow- definition-model. Soft-deps on base-register-seed-data: missing caseTypes warn + skip. - Add WorkflowDefinitionService::createDraft() for seed/import flows. Manifest-first: no new schemas or manifest pages — templates are seeded data. UI / controller / Vue store deferred to a follow-up apply (documented in builds/build.json deferred list).
Apply the inspection-checklists OpenSpec change in manifest-first style:
- Add two OpenRegister schemas in lib/Settings/procest_register.json:
- inspectionChecklistTemplate (REQ-IC-1): name, caseType, version, status
(draft|active|retired), sections[] with nested items[], failureAction
enums, seedKey
- inspectionChecklistRun (REQ-IC-2): case, inspection, template,
templateVersion, templateSnapshot, inspector (server-derived),
responses[], photos[], overallResult, syncState, followUpType
- Wire schema slugs into SettingsService::DEFAULT_SCHEMA_CONFIG_KEYS so the
manifest renderer can route auto-generated index + detail pages
- Add lib/Service/Inspection/ChecklistService.php — createRun snapshot
freeze + IUserSession-derived inspector, submitRun with REQ-IC-3
validation, REQ-IC-6 aggregateResult, REQ-IC-7 follow-up dispatch
(herinspectie / handhavingstaak via handhavingsactie / documentVerzoek)
and REQ-IC-8 append-only assertion
- Add lib/Listener/ChecklistRunImmutabilityListener.php — rejects
ObjectUpdatedEvent on submitted runs with "Checklist run is append-only"
- Register the new listener on ObjectUpdatedEvent in Application.php
- Record the change in builds/build.json
Manifest-first apply of bezwaar-lifecycle: - bezwaar schema with status enum (10 states) + AWB-grounded fields - x-openregister-calculations on bezwaar: decisionDeadline (AWB 7:10), dwangsom (AWB 4:17) — no BezwaarDeadlineService - SeedBezwaarWorkflowDefinition repair step publishes the canonical AWB workflow definition (12 transitions, requiredField guards on awbReference for Niet-ontvankelijk + Ingetrokken) - BezwaarLifecycleListener observes object events and routes them onto the status-transition-engine — no bespoke BezwaarController and no BezwaarLifecycleService - Manifest pages: Bezwaren (index) + BezwaarDetail (Overview/Advice/ Hearing/Decision/Audit tabs) + Bezwaren menu item
Manifest-first apply of the case-location change. Adds a `location` schema to the Procest register with a back-reference to `case`, the case detail "Locaties" sidebar tab (related-index widget filtered by case UUID), and admin index/detail pages for browsing all case locations. The location entity CRUD is handled entirely by the OpenRegister auto-form rendered by the manifest — no bespoke LocationController. LocationService.php exposes the server-side helpers the manifest cannot: - validate() — cross-field rules from design.md §Validation Rules - reverseGeocode() — delegates to PdokLocatieserverService (pdok-integration) with graceful null fallback when PDOK is unavailable - attachToCase() — persists a validated location through the OpenRegister object store, scoped to a case - listForCase() — server-side helper for workflow guards and map clustering Settings: adds `location_schema` to the app-config allow-list and the `location` → `location_schema` slug mapping so the schema UUID is resolvable at runtime. Spec tasks applied: T01 (schema + config key), T02 (back-reference relation docs), T04 (LocationService validate/reverseGeocode/attachToCase). Deferred: T03 (PDOK client supplied by pdok-integration #390), T05 (no bespoke LocationController — manifest covers CRUD), T06 (manifest-driven tab replaces Vue-side CaseLocationsTab.vue for MVP), T07/T08 (CSV import/export — follow-up), V01-V04 (test scope — strict watchdog lint+jq only).
Manifest-first implementation of the regulator-grade, append-only audit
trail for the parafeerroute lifecycle.
- Schema: add paraferingAuditEntry to procest_register.json (action,
actor, actorRole, timestamp, reason, contentSnapshot, ipAddress,
auditEntryHash) + register slug list + new x-pages index page at
/voorstellen/:voorstelId/audit-trail (sorted by timestamp desc).
- Service: lib/Service/Parafering/AuditTrailService.php — record() builds
the canonical entry + SHA-256 hash and persists via ObjectService;
export() returns the MDTO 1.0 envelope with 20yr/7yr retention;
assertAppendOnly() rejects UPDATE/DELETE with OCSForbiddenException
"Audit entries are append-only".
- Domain event: lib/Event/ParafeerTransitionEvent.php — emitted by
ParafeerRouteService (startParafering, completeStep, skipStep,
addAdhocStep, completed) and ParafeerActieService.recordAction.
- Listener: lib/Listener/ParaferingAuditListener.php subscribes to the
event and persists one entry per transition; failures are swallowed
so audit outages never block operational transitions.
- Validator: lib/Validator/ParaferingAuditAppendOnlyValidator.php hooks
OR pre-save (ObjectCreating/Updating/Deleting) events to enforce
append-only on the audit schema.
- Endpoint: GET /api/voorstellen/{id}/audit-trail/export (action, not
CRUD) — RBAC via auditors/secretariaat/beheerders groups; returns
metadata + entries envelope.
- Registry: SettingsService gets the parafering_audit_entry_schema
config key and slug→key mapping.
CRUD listing is served by OpenRegister's auto-exposed
/api/objects/<register>/<schema> endpoint via the manifest index page;
no bespoke CRUD controller introduced.
REQ-001 AI REST surface for classify/extract/ask/summarize/suggestRouting/suggestNext, REQ-002 Human-in-the-loop action recording and audit-trail listing, REQ-003 Global + per-feature AI enablement flags, REQ-004 Dutch PII stripping before model dispatch, REQ-005 Settings management and AI-model health check. Ghost change: retrofit-2026-05-24-ai-assistance. Refs #565.
REQ-001 Implement IMcpToolProvider with stable app id and hardcoded tool catalogue, REQ-002 listProcesses tool with bounded limit and optional status filter, REQ-003 getProcessDetails tool returning case + history, REQ-004 Per-object authorisation (assignee/role/admin) inside invokeTool, REQ-005 Standard error envelopes and result-cap. Ghost change: retrofit-2026-05-24-mcp-integration. Refs #565.
) Specifies procest's DSO intake adapter as a thin client to openconnector's full DSO protocol spec. Named *-client to avoid overlapping with openconnector's spec. REQ-001 DSO vergunningaanvraag intake creates procest zaak, REQ-002 DSO-specific case properties stored as side records, REQ-003 Procedure-type deadline duration lookup. Ghost change: retrofit-2026-05-24-dso-omgevingsloket-client. Refs #565.
REQ-001 Aggregate health-check JSON endpoint with status/version/checks shape, REQ-002 Three sub-checks (database/OpenRegister/filesystem) with severity tiering, REQ-003 HTTP status reflects aggregate health (200 ok, 503 error/degraded). Ghost change: retrofit-2026-05-24-ops-observability. Refs #565.
Describes observed behavior of the backend GIS proxy (GisProxyController + GisProxyService) as 2 new REQs on the map-component capability spec. Code already exists — this change retroactively specifies it. Refs #565
* retrofit: draft zgw-autorisaties-api spec + annotate 16 methods
Reverse-spec the VNG ZGW Autorisaties (AC) API served by AcController.
The coverage scan mislabeled this cluster bezwaar-advisory-committee
("AC" guessed as Adviescommissie); it is actually the Autorisaties
component managing applicaties via OpenRegister ConsumerMapper.
5 observed-behavior REQs: CRUD+mapping (REQ-001), ac-001 clientId
uniqueness (REQ-002), ac-002 heeftAlleAutorisaties consistency
(REQ-003), ac-003 scope-based field requirements (REQ-004), JWT auth +
safe degradation (REQ-005). Notes flag the authorization gap (JWT
authenticated but scopes never checked; UUID-only object resolution;
write-path skips validation) as observed, not fixed.
* retrofit: archive zgw-autorisaties-api change
Merge the spec delta into openspec/specs/zgw-autorisaties-api/spec.md
and move the ghost change to archive/, following the repo retrofit
convention (cf. retrofit-2026-05-24-annotate-procest).
* chore: ignore reverse-spec annotation commit in git blame
…#568) Drafts 5 numbered REQs describing the procest-side ZGW API surface (ZRC/ZTC/DRC/BRC/NRC controllers, ZgwService + helpers, ZgwAuthMiddleware, LoadDefaultZgwMappings repair step) and annotates every file in the cluster with a per-task @SPEC pointer. Ghost change: retrofit-2026-05-24-zgw-api-mapping (delta merged into main spec). Refs #565.
Drafts 5 numbered REQs describing the action-handler framework (interface contract, registry, notification + content + reminder handler families). Ghost change: retrofit-2026-05-24-automatic-actions (delta merged). Refs #565.
Ports the unique outbound WFS-export capability from PR #483 (feature/462/gis-integration) onto the current development baseline. That branch had diverged from an unrelated history root and could not be merged directly without reverting large amounts of merged dev work, so only the genuinely-unique, self-contained additions are carried over: - lib/Service/WfsExportService.php — builds a GeoJSON FeatureCollection of case locations from OpenRegister (uses SettingsService.getObjectService + findObjects, already on dev) plus a WFS GetCapabilities descriptor. - lib/Controller/WfsExportController.php — GET /api/gis/wfs (GetFeature) and /api/gis/wfs/capabilities, NoAdminRequired with an authenticated- session guard, typeName/outputFormat/maxFeatures/bbox/status/caseType params. - appinfo/routes.php — two additive wfsExport routes alongside the existing gis_proxy / wms_wfs proxy routes. - Unit tests for both classes. This complements dev's existing proxy-based GIS work (GisProxy/WmsWfs); the WFS export (procest cases AS a layer) was absent on dev.
Add documentationUrl config to the FeaturesRoadmap roadmap page so the footer nav item resolves, and bump @conduction/nextcloud-vue to beta.101.
Backs the shared @conduction/nextcloud-vue CnSupportDialog auto-mount's
per-user seen-flag. Adds GET/PUT /api/preferences/{key} backed by
Nextcloud IConfig user values (NoAdminRequired + NoCSRFRequired). Keys
are sanitised to a safe charset and namespaced under pref_. When the
caller is unauthenticated the widget falls back to localStorage.
Intercept the not-yet-implemented case-email-integration change so it
consumes the OpenRegister `email` integration leaf (NC Mail; id `email`,
group `comms`, storage `link-table`) for all email display, compose, link
and unlink — rather than building a bespoke mail client.
Re-pointed to the leaf (display + compose + link):
- emailMessage/emailThread schemas, CaseEmailService send/transport,
EmailComposer.vue, EmailThread.vue, EmailTab.vue, UnlinkedQueue.vue,
SMTP send config and send/list/link/unlink endpoints — all removed.
Display/widget via the leaf tab + CnEmailCard on the `case` detail page
(ADR-019/ADR-024); compose in NC Mail; link via the leaf's
POST /api/objects/{register}/{schema}/{id}/email.
Kept in-app as leaf extensions (no leaf equivalent):
- emailTemplate schema + EmailTemplateService versioning + Dutch defaults
(per-zaaktype templating; prefills an NC Mail draft).
- Email -> PDF -> caseDocument archival + retry (Archiefwet/ZGW).
Documented ADR-022 exception:
- InboundEmailJob polls a shared/functional mailbox (owner-less, so the
link-only leaf cannot ingest it) and auto-links by case-number subject
regex, recording links through the leaf endpoint. Justified in new
openspec/architecture/adr-002-shared-mailbox-poller-exception.md.
openspec validate case-email-integration --strict: valid.
…to-leaf chore(case-email-integration): re-point to email leaf per ADR-022
Add x-openregister-notifications to the case and task schemas so a newly created case or task assigned to a user raises an in-app notification to that assignee via the OpenRegister notification engine.
…te-16 to zero (#611) Bulk-inserts additive /** @SPEC openspec/... */ comment lines above every in-scope backend and frontend method flagged by the spec-coverage gate (csc.py --mode report). 1561 annotations across 310 files; uncovered_count 0 -> 0. Targets map each method to its file's existing class-level @SPEC reference where present, otherwise to the matching openspec change (tasks.md) or archived spec (spec.md) by feature area. Comment-only, no code logic changed (diff is purely additive: 1561 insertions, 0 deletions).
…hares/analytics/forms Five SPECS-ONLY migration changes moving procest from in-app NC features to OpenRegister integration leaves per hydra ADR-022: - migrate-maps-to-maps-leaf: Leaflet/WMS/WFS map UI -> maps leaf (geo data kept) - migrate-appointments-to-calendar-leaf: LocalBackend -> calendar leaf; Qmatic/JCC flagged as ADR-022 exception (external municipal systems) - migrate-public-share-to-shares-leaf: PublicShareController token sharing -> shares leaf - migrate-sla-dashboard-to-analytics-leaf: doorlooptijd charts -> analytics leaf (SLA compliance calc stays in-app as case-domain logic) - migrate-inspection-forms-to-forms-leaf: checklist/advice forms -> forms leaf, inspection photos -> photos leaf (immutability + photo-gate rules stay) CMMN/ZGW engine + parafering e-signature stay in-app (documented exceptions). All five pass 'openspec validate --strict'. No code applied.
chore(openspec): ADR-022 leaf-migration specs (maps/calendar/shares/analytics/forms)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.