Skip to content

feat(cmmn): retire the local CMMN runtime, the case plan lives in OpenRegister only - #3773

Merged
rubenvdlinde merged 212 commits into
developmentfrom
build/dq-l7-6
Oct 12, 2026
Merged

rubenvdlinde merged 212 commits into
developmentfrom
build/dq-l7-6

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What this does

dossiq no longer runs a case engine of its own. An adaptive case plan is rows in OpenRegister's case layer, and the case page reads them there and nowhere else (decision 146, one engine).

This is retire-cmmn-caseplanstate tasks 3.1, 3.3, 4.1 and 4.2, plus the e2e specs of group 5.

  • Backend removal (3.1, 3.3). lib/Service/Cmmn/ (nine classes), CmmnCaseController and the five /api/case/{caseId}/cmmn-plan* routes are gone. So are occ dossiq:cmmn:rollback-case-plans, CasePlanRollbackService and the cmmn_prefer_openregister_case_plan flag: all three existed only for the bridge release. Their unit tests went with them.
  • Frontend removal (4.1). cmmnApi.js and cmmnHelpers.js are gone. CasePlanPanel.vue asks OpenRegister only. A case OpenRegister holds nothing for shows "no adaptive plan"; an outage still shows an error with a retry.
  • The guard (4.2). tests/Unit/Service/CmmnRetirementTest.php fails on any file under lib/, src/ or appinfo/ that names the retired namespace, the controller, a cmmn-plan route or casePlanState. The allowlist holds the four drain files and the five files that declare or describe the property. Each entry gives its reason, a stale entry fails, and no entry may name anything but the blob.
  • A BPMN case stays untouched. CmmnBpmnCoexistenceRegressionTest tested the engine that is gone. Its invariant is now testTheStatusTransitionPathNeverReachesTheCaseLayer and an assertion in the case-detail e2e.
  • E2E (5.1 to 5.3), written, not run. tests/e2e/cmmn-retirement.spec.ts opens a case, completes an item from the panel, probes the old routes for 404 and proves a drained case shows the same states. case-detail-kpis-and-tabs.spec.ts loses its cmmn-plan and 409 allowances.

Nothing here is named after a procedure (decision 182). The plan panel, the projection and the guard serve any case type with handlingModel: cmmn.

Base and stack

Before you merge: the drain gate

Task 3.2 is not in this PR. casePlanState stays declared and the drain stays shipped.

After this PR a case that still carries a blob shows no plan until the drain has moved it. The repair step of #3605 runs on the same upgrade, so that window closes during the upgrade for every case the drain can map. A case the drain reports as unmappable keeps its blob and shows no plan until someone resolves it. If that is not acceptable for an instance, run occ dossiq:cmmn:migrate-case-plans --strict there first and merge on exit 0.

There is no rollback command any more. Rolling back means rolling back the release.

Verified

  • phpunit --no-coverage over tests/Unit/Architecture, AppInfo, Repair, Controller, Command, Service/CasePlanDrain, CasePlanProjectionServiceTest and CmmnRetirementTest: exit 0, 1960 tests.
  • Mutation check on the guard: a file under src/ naming casePlanState turns it red.
  • phpcs, phpmd, phpstan and psalm on the four changed lib/ files: exit 0 each.
  • vitest on the three case-plan specs: 33 pass. eslint and prettier on the changed frontend and e2e files: clean. openspec validate retire-cmmn-caseplanstate --strict: valid.
  • Hydra gates against the train base: 7 fail, all inherited. Gates 22 and 53 (manifest schema), 23 (TenantService, StateMachineService), 43 (DocumentReviewPanel.vue), 51, 104 and 107. None is in a file this PR changes.
  • The catch-return-null ceiling goes 264 to 263 (CaseModelLoader::getActiveModel is gone).
  • Not run: Playwright (decision 139). The full check:strict, npm run lint and full vitest were still waiting for a build slot when the lane handed back. CI is the full check (decision 149).

…them without a grant

Every curated read (deadline dashboard, lead times, KPIs, workload, available
transitions, overdue complaints) now declares reach: 'user'. Without it a
two-segment id resolves to external and needs an explicit grant.
Needs openregister build/dep-dossiq-mcptool-reach on the instance first.
… and work its terms

Curated write tools, each on the service its controller calls:
transitionCase (StatusTransitionService, guards and status record intact),
reassignCase (new CaseAssignmentService::reassign, coordinators only, the
receiver is notified), completeTask (the engine's complete verb),
extendDeadline/pauseDeadline/resumeDeadline (new TermTools: the term is read
and its CASE must be changeable by the caller before the term services run).
Refusals come back as envelopes with the service's own rule and sentence.

WIP: AppointmentTools and BeschikkingTools are listed but not written yet.
…chikking

AppointmentTools (scheduleAppointment, cancelAppointment; external reach: the
resident is told) run AppointmentController's case checks and call
AppointmentService. BeschikkingTools offers draftBeschikking only, through
BeschikkingService::compose(); approving, signing and sending stay with the
caseworker. A test holds the tool classes off ObjectService.

hermiq-ai-tooling 3.1-3.3 ticked; the spec names the services as found.
# Conflicts:
#	lib/Settings/register.d/60-termijnbewaking.json
#	tests/schemas/schema-version-digests.json
… verdict

woo-review-triage section 1: wooDocumentReview, wooTriageRule and
wooReviewBatch schemas (87-woo-review.json, register 0.20.23); only an
in-scope document needs a verdict and an unmarked one is always outstanding;
POST relevance and GET summary on the Woo case; a repair step marks documents
assessed before this change in scope so they do not block their case.
(unit tests written, run pending a slot)
# Conflicts:
#	lib/Settings/register.d/60-termijnbewaking.json
#	tests/schemas/schema-version-digests.json
…duleAppointment (phpmd finds nothing without it)
…1-4)

Decision 179 (Ruben, 10 Oct 2026): a form submits into its destination object, with no in-between intake layer, and is valid against that object's schema when built. Cross-app change: hydra openspec/changes/form-submits-into-its-destination-object (ADR-117, hydra#748). Spec only.
# Conflicts:
#	appinfo/info.xml
#	tests/schemas/schema-version-digests.json
…rmed only when its term runs

WooRequestIntake::receive() takes opencatalogi's answer keys (portal-form and
opencatalogi origins, subjectRef optional for those two), writes the Woo case
directly (decision 179: no intake object in between), counts the term from
when the requester sent it, and reads the term back: armed only when a
statutory instance with an engine timer ends on the case deadline; otherwise
not-armed with an internal timeline entry, or unavailable with nothing
written. PortalContributionProvider::receiveWooRequest() has opencatalogi's
signature. Register 0.20.26 (wooRequest origin enum, phone and address).
…t, a due invoice and a published award

Four x-openregister-notifications rules on the supplier schemas mail the
supplier's contact address through supplierRef, using OpenRegister's new
email recipient kind (portal-contribution T3).
…nd act as dossiq on the case layer

The bridge called createPlan and getPlan with uid null, which OpenRegister
refuses, so every case start created nothing. It now calls the system verbs
from openregister#4544 and names an OpenRegister that lacks them.

The drain (retire-cmmn-caseplanstate 2.1-2.3): occ dossiq:cmmn:migrate-case-plans
and a post-migration repair step hand each blob to ensureItems with the
recorded states and the event log as history, verify every state, then clear
the blob. Unmappable cases keep their blob and are reported.
…chable

formerReferences (list of {application, reference}) on the case schema, an
exact search declaration, register 0.20.27.
OpenCatalogiWooCase maps every status to its stage (received, in_progress,
awaiting_clarification, decided, withdrawn), carries start, requester,
channel, old reference, extension count and end date, on opencatalogi's own
request shape as fixture. First part of takes-over 4.2.
…s no approval floor

Decision 176 (Q-dossiq-L6-2): REQ-MCP-206 now says hermiq decides which AI
writes wait for a person. dossiq declares readOnlyHint: false and a scope on
every write so hermiq can gate it, and ships no grant or waiver. CHANGELOG
entry for the curated tools (task 4.3).
…who gets a task for it

POST /api/cases/{id}/woo/batches takes a list of documents or a filter on
the rule that marked them, refuses a document already in another open
batch (409, naming the batch), writes the batch on each review and hands
the reviewer an engine task. GET lists the batches with assessed of total.

Also: WooThroughputReport gets its row in the date-arithmetic audit that
development's EveryTermOnTheCalendarTest now requires.
…siq.fileCase

dossiq.fileCase carries the citizenIntake mark with scope and action create,
which is how hermiq now recognises an intake tool (decision 177). It writes
through the create form's own path: one save on the case schema as the caller,
so the schema, the intake requirements, the duplicate policy and RBAC all run,
and a refusal is thrown so hermiq hands the resident to a person. No
intermediate intake object (decision 179). The six curated reads carry
outsideAgent for hermiq's outbound surface.

ai-features-on-the-case-consume-hermiq 5.1, 5.2; screens.md added.
…d the viewer reports the pages seen

Review depth per document type (every page, or a sample drawn from a seed
recorded on the review). POST .../documents/{documentRef}/pages-seen
appends each displayed page with the reviewer and time; the first page
count a review hears sets its required pages from the recorded depth.
caseType.aiFeatures (register fragment 43) maps hermiq's feature slug to
case, intake or none, and holds no provider, model or residency. Register
0.20.25, info.xml bumped, en/nl strings. Ticks ai-features 1.1, and 1.2/2.1
with the files that already carry them.
# Conflicts:
#	.debt-baseline.json
#	appinfo/info.xml
#	appinfo/routes.php
#	lib/AppInfo/Registrar/AppHostRegistrar.php
#	lib/Service/Settings/SchemaSlugMap.php
#	lib/Settings/dossiq_register.json
#	lib/Woo/WooRequestIntake.php
#	openspec/changes/archive/2026-10-10-custody-and-handover-of-a-case/screens.md
#	openspec/changes/archive/2026-10-10-frozen-beschikking-and-numbered-successor/screens.md
#	openspec/changes/archive/2026-10-10-site-resident-portal-design/screens.md
#	openspec/changes/portal-contribution/screens.md
#	openspec/changes/woo-request-takes-over-from-opencatalogi/screens.md
#	openspec/changes/woo-review-recall-and-stopping/screens.md
#	openspec/changes/woo-review-triage/screens.md
#	openspec/specs/beschikking-generatie/screens.md
#	openspec/specs/dossiq-sociaal-domein-avg-consent/screens.md
#	openspec/specs/portal-contribution/screens.md
#	tests/Unit/Architecture/catch-return-null.allowlist.json
#	tests/Unit/Repair/RepairStepRegistrationTest.php
#	tests/schemas/schema-version-digests.json
#	tests/vitest/caseCommunicationWidget.spec.js
…nRegister only

retire-cmmn-caseplanstate 3.1, 3.3, 4.1, 4.2 and the e2e of group 5.

lib/Service/Cmmn, CmmnCaseController, the five cmmn-plan routes, the
rollback command and the read-preference flag are gone. CasePlanPanel
reads OpenRegister's case layer and nothing else. CmmnRetirementTest
refuses a new reference and ties the casePlanState declaration to the
drain: they leave together (task 3.2, its own pull request).
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Full local run on 96a5cc4, once, through the build slot.

  • composer check:strict: exit 1, from one leg. lint, phpcs, psalm and phpstan pass. PHPUnit passes: 8045 tests, 7 skipped.
  • The failing leg is phpmd, with four findings: WooRequestIntake (complexity 53), WOODocumentAssessmentService (complexity 52, coupling 13) and WOODecisionService (coupling 13). They are the four the merge train (train: land 32 ready PRs (11 Oct, b) #3742) lists as its own. This PR touches none of those files.
  • npm run lint: exit 0. prettier on the changed files: exit 0.
  • Full vitest: 3281 pass, 2 fail. validateManifestLag.spec.js and one test in caseTabConsolidation.spec.js compare against the installed @conduction/nextcloud-vue. My node_modules is a link to a checkout pinned one minor higher (2.80 against this branch's 2.79), so read those two in CI, not here. Neither spec is changed by this PR.

@rubenvdlinde
rubenvdlinde merged commit 2c6eb27 into development Oct 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant