Skip to content

feat(ai): the assistant panel on the case renders AI features, refusals and the report group - #3648

Merged
rubenvdlinde merged 39 commits into
developmentfrom
build/dq-l6-8
Oct 12, 2026
Merged

rubenvdlinde merged 39 commits into
developmentfrom
build/dq-l6-8

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Stacked on #3629 (build/dq-l6-6), which stacks on #3625 and #3607. Land those first. Development is not merged in here: it conflicts only in files that stack touches (appinfo/info.xml, .debt-baseline.json, catch-return-null.allowlist.json, schema-version-digests.json), and the CI shepherd resolves those on the base PRs.

What this builds

ai-features-on-the-case-consume-hermiq, tasks 3.2, 4.1 and 8.1, plus the 8.2 e2e spec. Board DqZaakAssistent.

  • src/components/case/CaseAiFeaturesPanel.vue (new), wired as the assistant sidebar tab on CaseDetail (src/manifest.json, src/registry.js).
    • Every feature the case type places on the case shows where it runs, as hermiq answers it. A feature hermiq does not offer reads as unavailable, never as local.
    • A run takes a case document and goes through caseAiFeatureGateway (feat(ai): a document-reading AI feature carries its reference to hermiq, and is refused before sending without one #3629) in the handler's own session.
    • A refusal shows as a warning card with refusalSentence and Gate: <gate>. A transport failure gets its own separate line.
    • "Reports like this one": the group count, plus the near-duplicates (hermiq's uncertain members) linked to their cases.
  • src/services/caseAiFeaturesApi.js (new): the reads. It reads the case type's aiFeatures declaration from OpenRegister first, so a case type that declares nothing makes no request about features. A failed read returns null, not an empty list.
  • Spec: added the scenario "A report shows the count of its group", which covers what the case shows. The queue scenario ("Two hundred reports read as one item") is new task 4.3, still open. The board covers the case only.
  • lib/AppInfo/Registrar/RepairPortRegistrar.php: removed @psalm-suppress UnusedClass, because ServiceRegistrar instantiates the class. That suppression (from 8609a83 on feat: build open OpenSpec changes (dossiq L6, part 3): agent write tools with reach #3583) is what turned check:debt-ratchet red on feat(ai): a document-reading AI feature carries its reference to hermiq, and is refused before sending without one #3629 (psalm.inlineSuppressions 287 > 286).
  • tests/vitest/caseTabConsolidation.spec.js: added assistant to the exact sidebar tab list, with the reason.

New code and its tests (coverage guard): CaseAiFeaturesPanel.vue -> tests/vitest/caseAiFeaturesPanel.spec.js (8 tests). caseAiFeaturesApi.js -> tests/vitest/caseAiFeaturesApi.spec.js (11 tests). No new PHP class.

Verified (exit codes)

  • vitest, 107 files (the specs that read manifest/registry plus the touched specs): 1 failure, fixed in caseTabConsolidation. After the fix that spec passes 23/23, exit 0. Touched specs 8/8 and 11+5, exit 0.
  • eslint and prettier on touched files: 0. tsc -p tests/e2e: 0. l10n check, check:l10n-js, check:schema-l10n: 0. debt-ratchet: 0.
  • Hydra gates (ci-gates.sh): exit 3, only the inherited gates 22/23/53 (47 manifest findings here, 48 on development).
  • check:strict did not run (slot rule, decision 149). The only PHP change is removing a docblock line. CI is the full check.

Still owed

  • 8.2 live pass (decision 139): npx playwright test tests/e2e/ai-features-on-the-case.spec.ts. hermiq's answers are routed in the browser, so no hermiq instance is needed.
  • 4.3 queue collapse, 5.3 request catalogue, 6.1 prompt library.

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
…iq, and is refused before sending without one

src/services/caseAiFeatureGateway.js runs a case type's feature on a case
document through hermiq's run-on-document endpoint (hermiq#1148), in the
handler's own session, so hermiq reads the document as the handler. A request
without a usable file id is refused before any request; hermiq's refusal
comes back carrying its gate, and refusalSentence() names the feature and the
reason. Ticks ai-features 3.1.
… absent-hermiq test declares the classes it uses
…ls and the report group

ai-features-on-the-case-consume-hermiq 3.2 and 4.1 (board DqZaakAssistent):
CaseAiFeaturesPanel as the case sidebar tab 'assistant'. Features show where
they run as hermiq answers; a run on a case document goes through the gateway
and a refusal names the feature, the reason and hermiq's gate. The report group
shows its count and the near-duplicates. The case type's declaration is read
first, so an undeclared case type asks nobody. Also drops a redundant
psalm-suppress on RepairPortRegistrar (debt ratchet).
@rubenvdlinde
rubenvdlinde merged commit bfc626e 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