Skip to content

feat(ai): a new case is placed once in hermiq's report grouping, and its group is read on the case - #3625

Merged
rubenvdlinde merged 35 commits into
developmentfrom
build/dq-l6-5
Oct 12, 2026
Merged

rubenvdlinde merged 35 commits into
developmentfrom
build/dq-l6-5

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

dossiq asks hermiq which group of similar reports a new case belongs to, and shows that group on the case (ai-features-on-the-case-consume-hermiq, REQ-AIC-04, design D-4). Also adds the 2.2 test.

  • A case type turns grouping on with aiFeatures["report-similarity"]. That is hermiq's own name for the feature: its endpoint and its audit action both use it.
  • A case is placed once. hermiq's report-similarity/evaluate adds the report to a group, so a second call would count the same resident twice. So ReportGroupingOnCreateListener queues ReportGroupingJob, which runs as the background service account, places the case, and stores the group id on the case as case.reportGroupId (register fragment 44, register 0.20.26). A case that already has an id is never sent again.
  • GET /api/report-group?caseId= (ReportGroupController::show) reads the group as it stands now (count, near-duplicates, terms, window) through the new HermiqAiFeatureClient::group(). It never places the case. A case the caller cannot read gets 404.
  • A group reduces no confirmation of receipt: confirmationsOwed() counts the distinct reports and takes no group. The acknowledgement listener is untouched.
  • An undeclared case type, or one set to none, sends nothing to hermiq and writes nothing.

Generic per decision 182: report grouping is a capability any case type can turn on. No class is named after a procedure.

Tasks

  • 2.2 ticked: HermiqAiFeatureClientTest (with hermiq absent: an empty map and no request; unreachable: an empty map; a refusal keeps hermiq's gate) and AssistantControllerTest::testAnAbsentHermiqReportsEveryDeclaredFeatureUnavailable, which runs over the real client.
  • 4.2 ticked.
  • 4.1: the backend is built. Showing the count and the near-duplicates on the case (Vue, board DqZaakAssistent) is still open.

Stack

Stacked on #3607 (build/dq-l6-4, head 09f75d1 merged in), which is stacked on #3583. origin/development is merged in too.

Verified locally (targeted, decision 149)

  • New tests: ReportGroupingConsumerTest 9/9, ReportGroupingJobTest 3/3, ReportGroupingOnCreateListenerTest 2/2, ReportGroupControllerTest 3/3, HermiqAiFeatureClientTest 5/5, plus ObjectListenerRegistrarTest 4/4. A mutation that removes the place-once guard turns ReportGroupingConsumerTest red.
  • Directories, all exit 0: tests/Unit/Architecture 96, AppInfo 79, Controller 1138, Settings 298, Service/Ai 52, Service/Assistant 26, Listener 323, BackgroundJob 62, Mcp 11, Service/Intake 7.
  • Touched lib files: php -l, phpcs, phpstan, psalm, phpmd and phpmd-unusedparams all exit 0. phpstan reports 2 findings in appinfo/routes.php at line 1221, a line this PR does not touch (inherited).
  • l10n: check-l10n and check-schema-l10n exit 0. Schema digests are recorded for fragments 43 and 44.
  • Bookkeeping: one catch-return-null entry added (ReportGroupingConsumer::readCase, a read-miss; ceiling 259 to 260, with a note in the file). Two IJobList::add() rows added to the date-arithmetic audit.
  • composer check:strict and the npm chain were not run: both build slots had 26 jobs waiting (slot rule). CI is the full check.

Twelve schemas declare a curated x-openregister-mcp block, so OpenRegister
derives 20 read tools (dossiq.case.search, dossiq.statusRecord.search, ...).
The hand-written DossiqToolProvider and its helpers are deleted, so nothing
shadows the derived tools. OpenRegister RBAC is the only gate.

Archives dossiq-mcp-adoption; live tasks 4.1-4.4 stay owed (decision 139).
A change outside properties/required/authorization only reaches an instance
when the schema version moves (SchemaVersionFloorTest). Digests regenerated.
…nd workload figures

First IMcpScannableServices opt-in (DossiqScannableServices, registered under
OpenRegister's per-app alias). Curated #[McpTool] reads: getDeadlineDashboard,
getDoorlooptijdMetrics, getKpiOverview, getWorkload (ReportingTools),
listAvailableTransitions (CaseTools) and listOverdueComplaints on
ComplaintService, a projection that never carries the complainant. Each tool
runs the gate its controller runs, then calls the owning service.
…creens.md for the touched specs and the archived change
# Conflicts:
#	lib/Settings/dossiq_register.json
#	lib/Settings/register.d/60-termijnbewaking.json
#	tests/schemas/schema-version-digests.json
# Conflicts:
#	lib/Settings/dossiq_register.json
#	lib/Settings/register.d/60-termijnbewaking.json
#	tests/schemas/schema-version-digests.json
…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
# Conflicts:
#	.debt-baseline.json
#	appinfo/info.xml
#	lib/Settings/dossiq_register.json
#	lib/Settings/register.d/60-termijnbewaking.json
#	tests/schemas/schema-version-digests.json
# Conflicts:
#	lib/Settings/register.d/60-termijnbewaking.json
#	tests/schemas/schema-version-digests.json
…duleAppointment (phpmd finds nothing without it)
…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).
…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.
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.
…spelled-out alias (gate 57 could not see AppointmentTools)
…y in CaseIntakeFiling (phpmd), REQ-MCP-206 anchor follows the rewritten heading (gate 46)
…its group is read on the case

A case type declares grouping as aiFeatures["report-similarity"]. hermiq's
evaluation adds the report to a group, so asking twice would count the same
resident twice: a created case is placed once, off the request, by
ReportGroupingJob as the background service account, and the group id is kept
on the case (case.reportGroupId, register fragment 44, register 0.20.26).
GET /api/report-group reads the group as it now stands, with the count and the
near-duplicates, and never places the case again. A group reduces no
confirmation of receipt: confirmationsOwed() counts the reports.

Ticks ai-features 2.2 (HermiqAiFeatureClientTest, absent hermiq over the real
client) and 4.2; 4.1 backend built, the render on the case is open. Records
the schema digests for fragments 43 and 44.
# Conflicts:
#	tests/schemas/schema-version-digests.json
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 65c78de

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ❌
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-manifest ✅
check-vue3-compile ✅
test-l10n ✅
format ✅
check-schema-l10n ✅
check-l10n-js ✅
check-debt-ratchet ✅
check-types-e2e ✅
composer ✅ ✅ 109/109
npm ✅ ✅ 659/659
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ❌ 7051/7052 · 3 skipped
Newman ⏭️
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ❌

Quality workflow — 2026-10-10 16:35 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 517e419

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-manifest ✅
check-vue3-compile ✅
test-l10n ✅
format ✅
check-schema-l10n ✅
check-l10n-js ✅
check-debt-ratchet ❌
check-types-e2e ✅
composer ✅ ✅ 109/109
npm ✅ ✅ 659/659
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ❌ 7051/7052 · 3 skipped
Newman ⏭️
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ❌

Quality workflow — 2026-10-10 17:03 UTC

Download the full PDF report from the workflow artifacts.

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