feat(flow): automatic actions become OpenRegister flows, and the dead surface goes - #1343
Merged
Merged
Conversation
The app-id rename (#1329) rewrote 1765 files and did not reach this directory, because these fifteen nodes landed after it. So an OpenRegister node catalogue served `procest.action.sendEmail` under an app named Dossiq — the flow-node id is one more channel the rename inventory has to cover, alongside the ones info.xml already documents. BREAKING, deliberately and with the tradeoff named. A node id is a runtime lookup: an OR flow referencing `procest.sendEmail` now resolves to nothing, and a missing node is silent — no error, the step simply has no node. Registering both ids with the old one deprecated was the alternative and was declined: these nodes are days old, field usage is effectively nil, and a permanent alias buys compatibility nobody needs at the price of a catalogue that shows every action twice forever. The class names move with the ids (ProcestActionNode -> DossiqActionNode, and so on for all nineteen files) — a class called Procest* inside namespace OCA\Dossiq is the same half-finished rename read from the other side. BOTH ID SPACES STAY DISTINCT. `dossiq.*` is the live transition vocabulary the dispatcher fires; `dossiq.action.*` is the configured-action catalogue. Both still ship a sendEmail, so SideEffectDispatcherTest::testItResolvesTheLiveIdSpace keeps proving the dispatcher resolves the live space and not the catalogue — crossing them would run a different class with different config keys. Also renamed the personal-settings mount id (`procest-personal-settings` -> `dossiq-`): template and mount call are one pair, no stored data, nothing else reads it. LEFT ALONE, on purpose: `procest.tenant.*` / `procest.parafering.*` audit action keys and the `Open Registers/Procest` register folder are values already written into stored rows and on disk — info.xml calls them frozen and they are. Likewise the historical issue refs, archived @SPEC paths, MigrateUserPreferences' OLD_APP_ID, and `procestermijn`, which is a Dutch word and not the app. LIVE-VERIFIED: GET /api/flow/node-catalog returns 45 nodes, 15 dossiq.*, zero procest.*. Suite 2406 green, phpcs 0 errors, phpmd 0, phpstan [OK].
… surface goes `automaticAction` objects have never executed. `SideEffectDispatcher` runs the SEPARATE `Service\Transitions` vocabulary keyed on an inline `type`; nothing has ever read these objects at run time. So `/settings/automatic-actions` was an admin surface over a capability with no runtime behind it — the same shape as the AVG page C1 retired. MIGRATION (`occ dossiq:actions:migrate-to-flows --user=<uid> [--dry-run]`). Each action becomes a flow whose single action node is the `dossiq.action.*` node this app already contributes, wrapped in a manual trigger and an end so OpenRegister will actually run it. That is what makes the configuration executable for the first time. IT IS A COMMAND, NOT A REPAIR STEP, AND THAT WAS FORCED. `FlowService` refuses to create a flow without a signed-in owner AND an active organisation — it throws rather than store an orphan that could never be seen, run or edited again. An upgrade runs as nobody, and `runAsSystem()` elevates RBAC while leaving both null, so an upgrade-time migration could only ever have thrown. `--user` is required and has no default: the flows inherit that identity permanently, so a guessed owner would be permanent too. THE FLOWS LAND ENABLED, which the user chose with the consequence stated: these are configured emails, webhooks and document generation that have never fired, and enabling them means they start. `--dry-run` exists for exactly that reason and the description says so. An action type NO node implements is SKIPPED, not wrapped. Writing a flow around an unresolvable node would rebuild the defect the previous commit just fixed in the VTH catalog — stored config naming a handler nothing answers to, reporting success and doing nothing. Idempotency comes from a `dossiq:automaticAction: <tenantId>:<slug>` marker stored on the flow itself, so a re-run rediscovers the link from the flow rather than from a ledger that can be lost; the marker is built from two schema-REQUIRED fields read WITHOUT defaults, because defaulting either would collapse several actions onto one marker and each migration would overwrite the last one's flow. RETIRED: both `/settings/automatic-actions` pages, and `ActionRegistry` + `ActionHandlerLocator` (556 lines, zero consumers — grepped across lib, src, tests and routes). The menu entry becomes a deeplink to `/apps/openregister/#/flows`, hash-routed, which is the trap C1 hit. The six handler classes STAY: the flow nodes are built on them, so they are now live code for the first time rather than dead code. TWO SCHEMA DESCRIPTIONS WERE LYING and are corrected in place rather than deleted, because the lie is the useful part of the history: `automaticAction` claimed its slug was "resolved at dispatch time by ActionRegistry", and `isPublished` claimed "only published actions are dispatched by SideEffectDispatcher". Neither was ever true. A reader who trusted them would conclude the objects worked. Also fixed: the Configuration menu link still pointed at `/settings/admin/procest` after the rename, so it 404'd. Tests (6): the create path asserts trigger+action+end and the decoded config; the skip path proves an unimplemented type writes nothing; the update path proves a re-run touches the existing flow instead of making a second; an unidentifiable action fails rather than sharing a marker; a dry run writes nothing; and one throwing action does not abort the rest. Suite 2412 green, vitest 343 green, phpcs 0 errors, phpmd 0, phpstan [OK], webpack builds.
rubenvdlinde
requested review from
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
August 24, 2026 07:38
…meetings (D1) decidiq owns agenda-building and meetings, and already surfaces them on a dossiq case through the `decidesk-decisions` integration leaf. The two pages here were the second implementation of the same thing. THE BLOCKER WAS REAL AND IS NOW GONE, VERIFIED AT RUNTIME. This retirement was held back because dossiq's leaf showed a "Besluitvorming unavailable" fallback until decidiq shipped its init-script wiring. That wiring is on decidiq `development` and deployed. Checked in the browser on a dossiq page rather than by reading decidiq's source: `OCA.OpenRegister.integrations.list()` returns 28 leaves including `decidesk-decisions` (label "Besluitvorming") carrying tab, widget, widgetCompact, widgetExpanded and widgetEntity surfaces.⚠️ THE PR NUMBER IN THE BLOCKING TASK WAS WRONG. It named "decidesk PR #100", which in both decidesk and decidiq is "chore(spec): merge p2-minutes-and-decisions-other-t2", merged 2026-04-19 — an unrelated change. A reader checking that reference would have concluded the blocker still stood, or that it had cleared four months ago; neither is what happened. Verifying by CONTENT is the only check that catches a citation like that. REMOVED: `/besluitvorming/agenda` + `/besluitvorming/vergaderingen/:id` and their manifest fragment · AgendaCompilerView, VergaderingDetailView, AgendaItem · their registry entries · AgendaController + AgendaService + its test · the two `agenda#` routes · three API-client functions. The chain is followed all the way down deliberately. Leaving AgendaController behind would leave routes nothing can reach, which is the dead-endpoint shape this programme already retired once on the AVG page; leaving AgendaService would leave a service with no caller and a test that keeps it green. `generateAgenda` was worse than unused: it POSTed to `/agenda/generate`, and no route for that has ever been declared. Every call it made 404'd. Also removed: the `BesluitvormingGroup` menu entry, which had no route, no href and — since nothing in the menu declares a parent — no children either. The renderer drops it silently, so it was invisible rather than broken; it is still config asserting a group that does not exist. KEPT: BesluitPublicatiePanel and `publishBesluit` (DROP/LVBB publication is a real capability with a live route), and `mandaat#mandaatCheck`, whose route serves the server-side guard even though the client function had no caller. Noted, not fixed here: BesluitPublicatiePanel is registered but referenced by no manifest page, and `besluitvorming#activateTemplate` has no frontend caller. Suite 2406 green, vitest 343 green, phpstan [OK], webpack builds.
Nextcloud's Dispatcher binds a controller argument BY NAME —
`$this->request->getParam($param, $default)` — never by position. Nine routes
declared a placeholder whose name no parameter answered to, so the argument
resolved to null, the non-nullable string typehint threw a TypeError, and the
request came back 400 with an empty body. For any input. On every call.
MEASURED AGAINST THE RUNNING INSTANCE, before and after:
parafeerRoute#start 400 (empty) -> 500 {"error":"Starten van parafering mislukt"}
parafeerRoute#completeStep 400 (empty) -> 500 {"error":"Stap kon niet worden voltooid"}
parafeerRoute#skipStep 400 (empty) -> 400 {"error":"Geldig stapnummer is vereist"}
parafeerRoute#addStep 400 (empty) -> 500 {"error":"Stap toevoegen mislukt"}
voorstelBesluit#registerBesluit 400 (empty) -> 404 {"error":"Voorstel niet toegankelijk"}
subsidie#finalizeVaststelling 400 (empty) -> 400 {"error":"Kon vaststelling niet vaststellen"}
subsidie#signBeschikking 400 (empty) -> 400 {"error":"Kon beschikking niet ondertekenen"}
subsidie#publishBeschikking 400 (empty) -> 400 {"error":"Beschikking niet gevonden"}
caseRelation#destroy 400 (empty) -> 403 {"ok":false,"reason":"access_denied"}
Every "after" is a DOMAIN answer about the deliberately bogus id I sent. That is
the proof: the method body now runs. The before was the framework refusing to
call it at all.
Cause: the Dutch->English vocabulary sweep renamed the method parameters
(`$voorstelId`->`$proposalId`, `$beschikkingId`->`$decisionId`,
`$aardRelatie`->`$natureRelationship`) and left the URLs behind. The placeholder
name is not decoration; it is the binding key.
NOTHING CAUGHT THIS, AND THAT IS THE INTERESTING PART. The route exists. The
controller exists. The method exists. gate-6 (route-reachability) verifies
exactly those three facts and passes. Only the NAMES disagreed, and no check
compared them. A caller saw a bare 400 and would reasonably read it as "my
request was malformed".
THE GUARD (tests/Unit/AppInfo/RoutePlaceholderBindingTest.php) reflects over
every declared route and asserts that each required, non-nullable parameter of
its target method is named by a placeholder in the URL. Written for the five I
had found — it immediately found FOUR MORE I did not know about, all confirmed
dead against the live instance before I touched them. That is the whole argument
for writing the check rather than fixing the instances.
ALSO FIXED, and only visible once the route worked:
`BeschikkingService::publish()` called `find()` OUTSIDE its try block.
OpenRegister's find() throws on a missing object rather than returning a
non-array, so the throw escaped as an HTML 500 — and the `is_array()` guard
below it could never fire, making 'Beschikking niet gevonden' a message no
caller could ever receive. `sign()`, ten lines down, has always wrapped its call.
Now they match, and that message is reachable.
E2E (5 new tests, all passing against a live instance): a retirement is the one
change nothing else in the suite can catch — every other spec asserts something
renders, so deleting a page leaves them green by not running.
tests/e2e/spec-coverage/retired-surfaces.spec.ts asserts BOTH halves: the old
view is gone AND its replacement is reachable. Asserting only the first would
pass just as happily on a build where the capability vanished.
The first version of that spec failed for a reason that had nothing to do with
what it tested — a retired route falls through to the app root, so its
console-error assertion was grading the DASHBOARD's network traffic, which
legitimately 404s against an instance whose register is not seeded.
DOCS: docs/user-guide/admin/02-automatic-actions.md described a rule engine with
triggers, conditions and a "Last run" column. The schema has no trigger field and
no condition field, and nothing ever executed one. It documented a feature that
did not exist. Rewritten around the migration command and the flow editor, with
the correction stated rather than quietly dropped, and its five stale screenshots
removed along with the capture block that produced them.
Suite 2407 green, phpcs 0, phpmd 0, phpstan [OK].
…ma, not the property
`bacAdviceRequest` declares `bezwaar` — a REQUIRED property — whose $ref points
at the `objectionProceeding` schema. AdvisoryCommitteeService wrote the objection
id under `objectionProceeding`: the name of the SCHEMA, not of the property. So
every advice request ever created omits a required property and carries an
undeclared one instead.
WHAT A USER SAW. Nothing. BezwaarDetail's advice-request stats-blocks and its
"Adviesaanvragen" object-list filter on `bezwaar: @objectId`
(src/manifest.json:1852, 1871, 1906), so a bezwaar rendered NO advice requests
however many it had. An empty list is also exactly what a bezwaar with no advice
requests looks like, so there was never anything to notice. No error, no warning,
no failing test — the schema, the manifest and the service each held a
self-consistent view, and only the service's disagreed.
FIXED IN THREE PLACES, because a rename that only moves the write leaves the
reads pointing at nothing:
- the write now sets `bezwaar` (AdvisoryCommitteeService:178)
- both reads go through `objectionIdOf()`, which prefers `bezwaar` and falls
back to the legacy key — AND LOGS A WARNING when it has to. A silent
fallback would make the repair step look unnecessary: reads would keep
working, the manifest filters would keep showing nothing, and the only
symptom would stay invisible. Which is how this lasted.
- `BackfillAdviceRequestObjection` copies the legacy key onto the declared one
for rows already in the field.
The backfill reads the legacy key WITHOUT a default. A row carrying neither key
is skipped, not written with `bezwaar => ''` — that would satisfy the required
property while pointing at nothing, producing a row that looks repaired and is
still invisible to the filter that needed it.
NOT changed: the `objectionProceeding` key inside the audit-trail payload
(:199). That is a free-form label describing the referenced schema, which is
accurate, and rewriting it would change the meaning of entries already written.
Tests (5): the copy, a correct row left alone, an unrepairable row skipped rather
than blanked, idempotency across a re-run, and a no-op when OpenRegister is
absent — an upgrade must not fail because a projection could not complete.
Found while scoping D2 (bezwaaradviescommissie -> decidiq governance-body); it is
independent of that migration and worth fixing on its own.
Suite 2412 green, phpcs 0, phpmd 0, phpstan [OK].
Contributor
Quality Report — ConductionNL/dossiq @
|
| 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 | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 552/552 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-24 08:27 UTC
Download the full PDF report from the workflow artifacts.
…ctie path is the live one
dossiq had TWO parafering APIs. `/api/parafeer-actie` is the one the UI calls and
the one that works: it records the action, advances the proposal, applies
accordering effects and emits the audit event. `/api/parafeer-route/...` was a
parallel implementation of the same flow that has never served a single request —
its placeholder bound no argument, so every call answered 400 (fixed one commit
ago, which is what made the choice visible rather than theoretical).
REMOVED (2,627 lines): the four routes, ParafeerRouteController,
ParafeerRouteService (663 lines), ParaferingStepActivator, VoorstelRouteMapper,
ParaferingApprovalBridge (331 lines), parafeerRouteApi.js, AddStepDialog.vue and
two test files that kept the dead code green.
EVERY CAPABILITY IS ACCOUNTED FOR, not assumed:
start -> the `besluitvormingActivate` transition action, which runs
BesluitvormingParafeerService::activate(). Already live.
complete-step -> parafeeractie `parafered` / `accorded`. Already live.
skip-step -> parafeeractie `skipped`, which is in that endpoint's action
enum. SkipStepDialog is RE-POINTED at it rather than deleted,
so the "Stap overslaan" button works for the first time.
add-step -> no live equivalent. Adding a step mid-route is not in the
parafeeractie vocabulary, so the button goes with the API.
Stated rather than quietly dropped: this is the one thing
that stops being offered — and it never worked either.
ADR-022's OpenRegister ApprovalChain delegation goes too, and its own code shows
why it had to. `delegateToApprovalWorkflow()` returned early unless
`proposal.approvalChainUuid` was set; that property is not declared on the
`proposal` schema and was written in exactly one place — inside
`ParafeerRouteService::startParafering()`, which was itself unreachable. So the
bridge short-circuited on every call, the "legacy in-array path" its comments
called a migration window was the only path there has ever been, and no approval
event dossiq raised has ever reached OpenRegister. Removing it changes no
behaviour; it removes a claim.
Verified against the running instance, not just the suite: the retired routes now
answer 405 (gone, rather than 400 = bound wrong), and a `skipped` action posted to
/api/parafeer-actie reaches the service and comes back with a domain answer about
the bogus id I sent it.
Suite 2402 green, vitest 343 green, phpcs 0, phpmd 0, phpstan [OK], webpack builds.
Contributor
Quality Report — ConductionNL/dossiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ⏭️ | ||||
| build | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-24 08:59 UTC
Download the full PDF report from the workflow artifacts.
…ed formatting
CI caught what my local run did not, in the one way that matters: it runs phpmd
TWICE, and only the second pass uses
`vendor/conduction/hydra-gates/quality-config/phpmd-unusedparams.xml`. I ran the
first config alone and read its silence as clean.
`ParafeerActieService::propagateDecision()` took `$proposal` solely to hand it to
`delegateToApprovalWorkflow()`, which the previous commit removed along with the
ApprovalChain bridge. With that gone the parameter had no reader — so the call
site was still passing an array nobody looked at. Removed from the signature, the
docblock and the call.
Also: `prettier --check` covers `{js,ts,vue,css,scss}` and two of my files were
unformatted. The three `src/manifest.d/*.json` warnings prettier also reports are
pre-existing AND out of the check's scope — JSON is not in that glob — so they
are neither caused nor fixed here.
Suite 2402 green, both phpmd configs clean, phpstan [OK], `npm run format` clean.
…ved and nothing said so
D4 asked whether dossiq's consumption of decidiq is render-and-read only. It is:
the leaf tab issues no write verb, dossiq writes nothing into decidiq's register,
and every cross-app command travels as a typed event. The audit passed — and
then found that the events themselves had stopped resolving.
decidiq renamed its namespace from OCA\Decidesk to OCA\Decidiq WITH NO ALIAS.
dossiq named only the old spelling, in two places, with two different failure
modes:
LISTENER (WorkflowListenerRegistrar) — guarded by class_exists, so it went
false and simply stopped registering. Every concluded decision quietly stopped
materialising a ZGW Besluit here. Nothing errored, and nothing could: a
class_exists guard that goes false looks EXACTLY like the optional app not
being installed, which is the case it was written for.
DISPATCH (ContractDecisionDelegationService) — fails closed, so it threw
"Decision service unavailable: decidesk is not installed" on an instance where
it was installed. Every contract decision blocked, behind a message pointing at
the wrong problem entirely.
PROVEN LIVE before and after, not inferred:
before: OCA\Decidesk\Event\DecisionConcludedEvent MISSING
OCA\Decidesk\Event\DecisionRequestedEvent MISSING
OCA\Decidiq\Event\DecisionConcludedEvent EXISTS
OCA\Decidiq\Event\DecisionRequestedEvent EXISTS
after: OCA\Decidiq\Event\DecisionConcludedEvent 1 listener (was 0)
Both constants now LIST the spellings, newest first, and resolve to the first
that exists. That is not politeness toward the old name — it is the only thing
that keeps the integration working through an upgrade where the two apps move at
different times, which is exactly the window this broke in.
An app cannot move another app's class name; it can only follow it. So
CrossAppEventNamesTest asserts the property worth holding: each constant is a
LIST, names the current namespace, still names the old one, and prefers the
current one. Pinning a single spelling fails the test now instead of silently
disabling a feature later.
NOT changed, deliberately: `sourceApp => 'procest'` and
DecisionConcludedListener::SOURCE_APP. That value is this app's id AS THE OTHER
APP KNOWS IT — decidiq echoes back what dossiq sends — so it moves only when both
sides move together. Its own comment already says so.
Suite 2406 green, phpcs 0, both phpmd configs 0, phpstan [OK], format clean.
The phpstan ignore is scoped to the one resolver method: neither namespace is on
this app's analysis path (that is the point of resolving them as strings), so
class_exists can never be proven true and the method reads as returning only
null. Same family as the ApprovalStepNotificationListener entries above it.
Contributor
Quality Report — ConductionNL/dossiq @
|
| 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 | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 552/552 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-24 09:42 UTC
Download the full PDF report from the workflow artifacts.
…lyses as real CI's phpstan rejected the previous commit with an error my container never produced: `class_exists() with '\OCA\Decidesk\Event…'|'\OCA\Decidiq\Event…' will always evaluate to false` (function.impossibleType). Neither namespace was on the analysis path, so the analyser proved the lookup dead — reporting a fix for a real outage as dead code. The root cause is the same either way: only the OLD namespace was ever stubbed. This adds `tests/Stubs/Decidiq/Event/*` alongside the existing Decidesk pair and scans both in phpstan.neon, so both spellings resolve and the resolution reads as what it is. That is better than an ignore, which would have silenced the symptom while leaving the analyser blind to the contract. The earlier `resolveRequestEventClass() never returns string` ignore is REMOVED, not kept alongside: with both classes resolvable, class_exists can return true and the method demonstrably returns a string. Two errors, one cause. The bootstrap now loops the two namespaces × two events rather than repeating a four-line block, and stubs each only when the real class is absent — unchanged behaviour, one place to add the next spelling. THREE TESTS PINNED THE OLD LITERAL and started failing the moment both stubs loaded: they imported `OCA\Decidesk\Event\*` and asserted the dispatched object was an instance of it, while the resolver now prefers the current spelling. That is the same defect as the production code had, one layer down — a test that names a class the code no longer builds. Pointed at the current namespace, with a comment saying why and pointing at CrossAppEventNamesTest, which guards the ordering they depend on.⚠️ Local phpstan passed on the previous commit and CI did not. The two disagree about which error fires, so `[OK] No errors` in the container is not proof the gate passes. The fix addresses the cause both report. Suite 2406 green, phpcs 0, both phpmd configs 0, phpstan [OK], format clean.
…old the wrong story
Two CI failures, both mine.
GATE-16 (spec-coverage): `BackfillAdviceRequestObjection::run()` and
`::getName()` are new PUBLIC methods and carried no `@spec`. The class had one;
the gate is per-method. Added.
E2E: `retired-surfaces.spec.ts` asserted the decision app registers its
`decidesk-decisions` leaf, and returned `null` for TWO different things — the
OpenRegister integration registry being absent, and the leaf being missing from
a registry that is present. Every null was then reported as "OpenRegister
integration registry not present".
So the message was wrong in both directions. dossiq's CI does not install the
decision app, so the registry is legitimately absent — an environment fact this
PR cannot affect — and it was reported as a failure. And had the leaf actually
gone missing with the registry present, that REAL finding would have been
described as an environment problem and dismissed.
A lookup failure must not wear the same words as a judgement. The probe now
returns three distinguishable outcomes:
registry absent -> test.skip() naming the reason. Not a defect here.
registry present, leaf missing -> a real failure, and the message now LISTS
what the registry actually holds instead of
guessing.
leaf present -> asserted as before.
Suite 2406 green, phpcs 0, phpstan [OK], format clean.
… hunk
Fifteen files conflicted, all in the Procest -> Dossiq Flow rename. They
collide on every hunk because the two sides also differ in indentation, which
makes the diff look far larger than the actual disagreement.
Resolved toward this branch, but checked rather than assumed. For each of the
24 hunks I compared the two sides with indentation and blank lines
normalised:
- nine Tx*Node files: this branch is a strict SUPERSET. It already carries
development's getDisplayName()/getDescription() additions and adds the
renamed nodeId on top.
- six files where development had lines this branch lacks: every one of
those lines is an old `Procest*` class name or a `procest.action.*` id —
precisely what this branch renames.
The case worth naming is SideEffectDispatcherTest. Development adds a test
asserting the dispatcher resolves the LIVE id space rather than the
catalogue's, because both action systems ship a sendEmail. That test is not
lost: this branch has the same test with `dossiq.action.sendEmail`.
Checked afterwards that nothing renamed too far. The surviving `procest`
strings are all correct:
- `procestermijn` / `procesType` in the ZGW validators are Dutch archiving
terms, not the app name.
- `procest.tenant.*` and `procest.parafering.*` in TenantAuditTrailService
are marked FROZEN PREFIX in the source and are already written into stored
audit rows, so they must not move with the app id.
php -l passes on all 22 touched files, and the listener catalogue resolves to
the renamed classes.
Contributor
Quality Report — ConductionNL/dossiq @
|
| 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 | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 552/552 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-24 19:13 UTC
Download the full PDF report from the workflow artifacts.
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.
Closes the C2 block of
page-topology-cleanup— observation 8 ("/settings/automatic-actions belongs in openregister as new flow functionality").What this does
procest.*→dossiq.*(the app-id rename missed this directory; the nodes landed after it)occ dossiq:actions:migrate-to-flows --user=<uid> [--dry-run]/settings/automatic-actionspages ·ActionRegistry+ActionHandlerLocator(556 lines, zero consumers)The finding this rests on
automaticActionobjects have never executed.SideEffectDispatcherruns the separateService\Transitionsvocabulary keyed on an inlinetype; nothing reads these objects at run time. The page administered a capability with no runtime behind it — the same shape as the AVG page C1 retired.Two schema descriptions asserted the opposite (
"resolved at dispatch time by ActionRegistry","only published actions are dispatched by SideEffectDispatcher"). Both are corrected in place rather than deleted: a reader who trusted them would conclude the objects worked.Why a command and not a repair step
FlowServicerefuses to create a flow without a signed-in owner and an active organisation — it throws rather than store an orphan that can never be seen, run or edited again. An upgrade runs as nobody, andrunAsSystem()elevates RBAC while leaving both null. An upgrade-time migration could only ever have thrown.--useris required with no default: the created flows inherit that identity permanently.These are configured emails, webhooks and document generation that have never fired. Enabling them means they start. That was a deliberate call (the stored config said what it wanted and was never honoured) —
--dry-runexists for it and the command description says so up front.An action type no node implements is skipped, never wrapped in a flow. Writing one would rebuild the
spawnCasedefect fixed earlier in this programme: stored config naming a handler nothing answers to, reporting success and doing nothing.Also fixed
The Configuration menu link still pointed at
/settings/admin/procestafter the rename, so it 404'd.Verification
[OK]· webpack buildsGET /api/flow/node-catalog→ 45 nodes, 15 of them this app's, both id spaces intact)🤖 Generated with Claude Code