refactor(ai): extract AiAuditService from AiService — clears the last phpmd finding - #722
Merged
Merged
Conversation
…licMethods) AiService carried 12 phpmd-counted public methods against a threshold of 10 — the one remaining phpmd finding on development. Move the three oversight methods (recordUserAction, listAuditEntries, recordAssistantAuditEntry) into a new OCA\Procest\Service\Ai\AiAuditService. That is a coherent responsibility split rather than a shuffle: AiService now does model orchestration only (is the feature on, build the prompt, scrub PII, make the one outbound call), while recording what a human did with a suggestion and reading the Algoritmeregister trail back live in AiAuditService. Storage stays where it already was, in AiAuditLog. 12 -> 9 counted methods. The model identifier (`<type>/<name>`) was needed by both halves, so rather than duplicate the config lookup it moves to a small AiModelIdentity, injected into each. Behaviour is unchanged. Call sites updated: AiController (now takes both services), AiAuditExportController and CaseAssistantService (audit-only, swapped outright), WOOAnonymisationAssistService (needs both — it also uses detectDeterministicPiiSpans). No DI registration change: these services are autowired, not bound in ServiceRegistrar. Also drops a dead @SuppressWarnings(PHPMD.ExcessiveParameterList) that was sitting on recordUserAction — the method has 7 parameters against a threshold of 10, so the annotation suppressed nothing. Verified by removing it and re-running phpmd. The extracted model-identifier logic had no test anywhere, so add coverage for AiModelIdentity and for AiAuditService's two write paths. Mutation-checked: flipping the `/` separator turns 3 of the new tests red. No suppression, threshold change or baseline was introduced; phpmd.xml, phpcs.xml, psalm.xml, phpstan.neon and phpunit.xml are untouched. phpmd on lib/ (595 files): exit 2 / 1 finding -> exit 0 / 0 findings. Positive-controlled both sides (injected probe yields 3 findings, exit 2). check:strict all green: lint 0, phpcs 0, phpmd 0, psalm 0, phpstan 0, phpunit 0 (1686 -> 1691 tests, 5632 -> 5649 assertions, 5 skipped unchanged).
rubenvdlinde
requested review from
WilcoLouwerse,
bbrands02 and
rjzondervan
as code owners
August 4, 2026 08:58
CI ran red on all 4 PHPUnit legs with 7 RISKY tests (failOnRisky=true): This test executed code that is not listed as code to be covered or used: - OCA\Procest\Service\Ai\AiModelIdentity phpunit.xml sets beStrictAboutCoverageMetadata="true", so a suite carrying @Covers must declare every class it actually executes. AiService now composes AiModelIdentity, so both suites that build a real AiService execute it. This was invisible locally because the check only fires when a coverage driver is present, and the local container has none — the gate's ABSENCE looked exactly like its success. Reproduced by building a container with Xdebug (CI uses Xdebug, not PCOV — PCOV does not drive this check at all) and running the same command CI runs. Positive-controlled: with the @uses line removed the run is exit 1 / 4 risky; with it restored, exit 0 / 0 risky.
Contributor
Quality Report — ConductionNL/procest @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 550/550 | |||
| PHPUnit | ❌ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ |
Quality workflow — 2026-08-04 09:17 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/procest @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 550/550 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ |
Quality workflow — 2026-08-04 09:33 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
added a commit
that referenced
this pull request
Aug 4, 2026
…figuration() (#725) phpmd on development flagged the last remaining finding: lib/Service/SettingsService.php:531 ExcessiveMethodLength The method loadConfiguration() has 102 lines of code. Current threshold is set to 100. It crossed the line in #721, which replaced the `+frag.<md5>` version suffix with a long explanatory comment. loadConfiguration() was doing two jobs: resolving the effective register configuration (read procest_register.json, parse it, deep-merge the ADR-037 register.d fragments) and importing it. Extract the first into a private readEffectiveConfiguration() returning ['data' => …] or ['error' => …], so loadConfiguration() stays a single import flow. Behaviour is unchanged — the same two failure shapes are returned to the caller in the same order. The extracted path had NO test: the only loadConfiguration test covers the OpenRegister-unavailable early return, which returns before any of this runs, so read/parse/merge could have broken silently. Add a test driving loadConfiguration() through to importFromApp, asserting it receives the on-disk version and parsed content, and that the version carries no `+` build suffix (the #721 invariant). Mutation-checked: returning an empty config array turns it red (0.13.2 -> 0.0.0). phpmd lib/ (597 files): exit 2 / 1 finding -> exit 0 / 0 findings. Positive-controlled both arms (injected probe yields 3 findings, exit 2). check:strict green: lint 0, phpcs 0, phpmd 0, psalm 0, phpstan 0, phpunit 0 (1691 -> 1692 tests, 5649 -> 5659 assertions). PHPUnit was run under Xdebug, because phpunit.xml sets beStrictAboutCoverageMetadata + failOnRisky and that check is INERT without a coverage driver — it silently caught nothing locally on #722 and only fired in CI. It flagged the missing @uses RegisterFragmentMerger here immediately.
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.
What
ConductionNL/procesthad exactly one phpmd finding left ondevelopment:This clears it with a real refactor — no suppression, no threshold change, no baseline.
How
AiServicewas 845 lines doing two jobs. The three oversight methods move into a newOCA\Procest\Service\Ai\AiAuditService:recordUserAction()listAuditEntries()recordAssistantAuditEntry()That leaves
AiServicedoing model orchestration only (is the feature on, build the prompt, scrub PII, make the one outbound guarded call), while "what did a human do with this suggestion" and "read the Algoritmeregister trail back" live inAiAuditService. Storage stays where it already was, inAiAuditLog— this is a surface split, not a second sink. 12 → 9 phpmd-counted public methods.The model identifier (
<type>/<name>) is needed by both halves, so instead of duplicating the config lookup it moves into a smallAiModelIdentity, injected into each. Behaviour unchanged.Call sites updated
lib/Controller/AiController.phprecordUserAction+listAuditEntries→ audit; 6 AI ops stay)lib/Controller/AiAuditExportController.phplib/Service/Assistant/CaseAssistantService.phplib/Service/WOOAnonymisationAssistService.phpdetectDeterministicPiiSpansNo DI registration change: these services are autowired; nothing in
lib/AppInfo/references anyAi*class.Also fixed
Dropped a dead
@SuppressWarnings(PHPMD.ExcessiveParameterList)that sat onrecordUserAction. The method has 7 parameters against a threshold of 10 — it suppressed nothing. Verified by removing it and re-running phpmd. Net: this PR removes one suppression annotation and adds none.Verification
Measured in a worktree pinned to
origin/development@6d650bec9, phpmd over 595 files inlib/, PHP 8.4 container (host PHP is 8.2 and dies inplatform_check.phpwith exit 255, which greps as a clean run).The positive control is the point: a clean phpmd run and a phpmd that never started look identical, so both arms were checked with a deliberately-dirty probe file.
composer check:strict, real exit codes (nottail's):Intermediate runs went through 38 errors → 23 errors → 0, so the suite demonstrably responds to this tree rather than passing by inertia.
New tests
The extracted model-identifier logic had no test anywhere in the repo, so it would have broken silently. Added:
AiModelIdentityTest— configured<type>/<name>, and thelocal/unknownfallback an unconfigured instance relies onAiAuditServiceRecordTest—recordUserAction()stamps the model + normalises nullactualValue/reason;recordAssistantAuditEntry()forwards verbatimMutation-checked: flipping the
/separator inAiModelIdentityturns 3 of the new tests red, so they assert something.Not done
@SuppressWarnings,@psalm-suppress,phpcs:ignore,phpstan-ignore,markTestSkippedor.skip(addedphpmd.xml,phpcs.xml,psalm.xml,phpstan.neon,phpunit.xml,composer.jsonuntouched