Skip to content

Release: merge beta into main - #1576

Open
rubenvdlinde wants to merge 27 commits into
mainfrom
beta
Open

Release: merge beta into main#1576
rubenvdlinde wants to merge 27 commits into
mainfrom
beta

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Brings main level with beta, which is 27 commits ahead.

development is green on its last completed push run with Playwright E2E passing, and beta was brought level with development earlier today, so this carries verified code rather than a snapshot of unknown state.

Security alerts are measured on main but fixed on development, so this promotion is what actually clears them.

github-actions Bot and others added 27 commits August 30, 2026 19:09
…190920

chore(sync): carry main back into beta
…0260830190930

chore(sync): carry beta back into development
vue-router 5 peers `vite: ^7.3.0 || ^8.0.0` and expects a Vite toolchain. These
apps build with webpack, which cannot resolve it at all: the build dies on
`Can't resolve 'vue-router'` from src and from @nextcloud/vue's own chunks.
Adopting it is a Vite migration, not a version bump.

Dependabot proposed it across 8 repositories in a single run, and merging any
one of them takes that app's build from green to red with no code change that
can fix it.

versioniq already builds with Vite and is the natural pilot if the fleet does
move. Lift this when an app's toolchain can actually take it.
#1536)

The comment claimed these apps "cannot resolve it at all". That is not what the
evidence shows, and a comment that overstates its case is worse than none: the
next person reads it, tries vue-router 5 somewhere it works, and stops trusting
the file.

Measured across four apps: integriq and zaakafhandelapp fail their build on
`Can't resolve 'vue-router'`, while openregister and learniq build clean on
5.3.0. The holdback still stands, for the honest reason rather than the
overstated one: a major that breaks some apps and not others cannot be merged
unattended, and the difference between them is not yet understood.

No behaviour change. The ignore rule is unchanged; only the reasoning is.
…ride it (#1539)

🔴 SECURITY. An inspector could escalate an enforcement measure past the
manager gate.

`LhsRecommendationService::override()` took the whole recommendation row from
the request and compared the requested intervention against the
`recommendedIntervention` IN THAT ROW. So posting a body claiming the matrix had
already recommended `bestuursdwang` made every lesser measure an
"override-down", and the manager check never fired. The same `array_merge` also
let the request restate `severity`, `behaviour`, `matrixVersion` and
`recommendedBy` on the persisted row — so the audit record of what the matrix
said would agree with whoever overrode it.

The controller's `groupManager->isAdmin()` check was real and is untouched. It
was guarding the wrong side of the comparison.

Now the service takes an ID and reads the row back from OpenRegister, and merges
only the five override fields onto it. `recommendationId` is the documented
shape; a whole `recommendation` object is still accepted from callers that have
not been updated, but ONLY its id is read.

Eight new tests pin it, and they fail against the old behaviour: restoring the
caller-supplied baseline turns two red, and rewriting one stored audit field
turns another red. `testOverrideRejectsANonObjectRecommendationWith400` asserted
the OPPOSITE of the new contract — "the engine expects the original row, not an
id" — which was the vulnerability stated as a requirement; it is renamed and
inverted rather than deleted, so the change of contract is visible.

Storage moved to a new LhsRecommendationStore. That was forced by a measurement,
not chosen for tidiness: the stored-row read took the service past the
class-complexity threshold, and moving the reads and writes out is the answer
that makes the service smaller rather than the one that silences the rule. The
service now needs neither SettingsService nor a logger, so both leave its
constructor.

Also removes the `LHS recommendations` settings-menu entry. A recommendation is
a per-enforcement audit record, not configuration — it belongs on the case, the
way the audit trail does. The PAGES stay routable so existing deep links still
resolve; only the menu entry goes.

Verified: 2559 unit tests pass (1 pre-existing warning in ZgwMappingServiceTest,
present on the base tree too), PHPCS/PHPMD/PHPStan/Psalm clean on every changed
file, and the manifest still validates against the v2 schema.
npm ci failed with ERESOLVE, so every frontend job failed before running
a single check.

Majors had landed WITHOUT the peers that must move with them. These
packages are a set: bumping one alone leaves another declaring a range
the new version cannot satisfy, and npm rejects the whole tree rather
than the single package. Fixing them individually walks the list,
because each correction exposes the next conflict underneath.

Realigned: @babel/core=^7.22.9 sass-loader=^17.0.0

Each range is the one npm itself demanded in the ERESOLVE it reported --
discovered by applying it and re-checking until resolution succeeded --
rather than a version chosen by hand.

Measured across the fleet: nine apps were unresolvable on this pattern.

Verified: the lockfile resolves from a clean tree, where it previously
exited on ERESOLVE.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…the referral as a flow (#1531)

* feat(bezwaar): hold advisory committees in the decision app, and run the referral as a flow

migrate-committees-to-decidiq has been BLOCKED on two prerequisites. Both are
now in place: decidiq#874 gave GovernanceBody the four fields a
bezwaaradviescommissie needs, and decidiq's governance-body-events change gave
it the typed command seam an in-process migration must use.

This is dossiq's half.

- CommitteeDelegationService dispatches GovernanceBodyRequestedEvent, guarded
  by class_exists across BOTH namespace spellings, and fails closed when the
  decision app is absent or did not handle the command. Shaped after
  ContractDecisionDelegationService, which is this app's working example of
  commanding the decision app by event.
- MigrateCommitteesToDecidiq raises a body per existing committee and records
  the id back. It REFUSES to run when OpenRegister exposes no runAsSystem(),
  rather than falling back to a bare call like the shared trait does: a repair
  step has no session, OpenRegister then resolves the actor as Anonymous and
  refuses every write, and $output->warning() does not fail an upgrade — so the
  fallback is exactly the silent no-op the spec forbids.
- dossiq.ensureCommittee is a flow node that does the same thing mid-run, and
  the seeded `Bezwaar advies` flow on bacAdviceRequest uses it as its first
  step: ensure the committee exists, the secretary opens deliberation, the
  chair issues the advice, each human step moving the request's status.
- governanceBodyId on bezwaaradviescommissie is the mapping record.

The roster is where the sharp edge was. `members[]` may also contain the chair,
and the seam keys a seat on (person, body) — so sending a uid twice overwrites
its first seat, and the committee silently loses its chair while every row
still looks well-formed. Officers are seated first and seen uids skipped;
testChairIsNotDemotedByAppearingInMembers pins it and goes red when the
de-duplication is removed.

Also fixed while here: DossiqFlowNodeListenerTest asserted a hard-coded count
of 17 nodes. Its sibling test's own comment warns against exactly that, and it
had already gone stale. It now derives from the fixture.

Verified: 2561 unit tests pass (1 pre-existing warning in ZgwMappingServiceTest,
present on the base tree too), PHPCS/PHPMD/PHPStan/Psalm clean on every changed
file, all 71 applicable hydra gates green, and the register fragment merge was
run through the real RegisterFragmentMerger rather than assumed.

* test(bezwaar): cover the committee flow node and the migration, and fix the mapping write they exposed

CI's coverage ratchet was right: this branch added a flow node and a repair step
with no tests of their own. 21 tests now cover both.

🔴 The first one written found a real bug. `recordMapping()` and the repair step
both wrote the mapping with `$committee + ['governanceBodyId' => $bodyId]`, and
PHP's `+` on arrays KEEPS the left operand's key. A committee row that already
carried an empty `governanceBodyId` — which is every row, once the schema
declares the property — kept the empty string. The mapping was never recorded,
so every run would re-dispatch, and the local side would never show a committee
as migrated. Both sites now use array_merge.

That is exactly the shape of defect the coverage gate exists to catch: the code
looked right, the migration reported success, and nothing recorded anything.

The node's tests pin the two behaviours it exists for — it FAILS the step when
the committee cannot be raised (so a run never continues past a committee in no
shared register), and it short-circuits on an already-mapped one (so a heartbeat
does not re-dispatch on every pass). The step's tests pin that it REFUSES
without runAsSystem() rather than warning, because $output->warning() does not
fail an upgrade and the silent no-op is the failure nobody notices.

One test-harness note worth keeping: the fake object service must implement
searchObjectsBySlug(), not findAll(). The SearchesObjects trait calls the
former, and a fake with the wrong method makes the step report "could not list
committees" while the assertions blame the migration.

Verified: 2582 unit tests pass (1 pre-existing warning), PHPCS/PHPMD/PHPStan
clean on both files.

* test(bezwaar): cover the branches the coverage guard named

The guard reported the exact shortfall: 86.69% (254/293 statements) on the
changed files against a 92.86% base, so 39 statements were uncovered. The
coverage artifact named them line by line rather than leaving it to guesswork.

They were all real branches, not filler: the dispatcher throwing, a committee
row arriving as an ENTITY rather than an array, a read that fails, a
best-effort mapping write that fails, members given as objects with their own
external flag, a members list that is not a list, entries with no usable uid,
an unusable relation value, the node's scope predicate, and the migration's two
catch arms.

Two of them are worth their own line. The entity-shaped committee: OpenRegister
hands a row back unflattened when the read did not expand it, and reading that
as an array finds nothing and reports the committee as missing. The failed
mapping write: it must NOT fail the step, because the decision app has already
raised the body by then and failing would discard work that succeeded.

Verified: 2593 unit tests pass, PHPCS and PHPMD clean.
…1542)

ADR-065 is explicit: OpenRegister is the only home for a flow engine, and a leaf
app that grows a second one is an ADR-022 violation. dossiq's `workflowTemplate`
is a state machine — statuses joined by guarded transitions — which is one of
the two shapes that ADR names, and the one symfony/workflow's StateMachine
models with `case.status` as the marking. OpenRegister already requires it.

The visible cost is in this app's own navigation: the settings foldout carries
BOTH `Flows` and `Workflow definitions`, side by side. Two authoring surfaces
for one concept.

- WorkflowTemplateFlowMigrator projects each template: one `dossiq.setStatus`
  node per status, one edge per transition.
- `occ dossiq:workflows:migrate-to-flows` with --user and --dry-run, mirroring
  the automatic-actions migration that already exists. A command and not a
  repair step, because FlowService refuses to create a flow without a signed-in
  owner and a repair step under `occ upgrade` has none.

🔴 THE FLOW ARRIVES DISABLED. The template still drives cases through
StatusTransitionService, so an enabled projection is a second thing driving the
same case: every status change would fire twice from the moment the migration
runs, and it would look like it worked. Adoption stays a deliberate act, which
is how the shipped x-openregister-flows arrive too.

🔴 STATUSES TRAVEL BY NAME. A statusType uuid is minted per installation, so a
flow carrying one is portable nowhere — which is exactly why `dossiq.setStatus`
exists as a node distinct from `dossiq.setField`.

Two smaller decisions:

A wildcard `fromStatus: '*'` contributes no edge. The seeder accepts it and no
shipped template uses it; an edge with no source node is not drawable, so it is
skipped rather than guessed at.

A re-run resolves the existing flow by a provenance marker in its notes, not by
name. A name is editable in the flow editor, and matching on one would mint a
second flow the moment somebody renamed the first.

Out of scope: retiring workflowTemplate or collapsing the two menu entries. The
projection has to be adopted and proven first, and the definition still carries
per-step SLAs, checklists and roles the projection does not. Removing the
definitions page while it is the only authoring surface would take away the way
to edit a live workflow.

Verified: 2572 unit tests pass (1 pre-existing warning), PHPCS/PHPMD/PHPStan
clean. Mutation-checked: creating the flow enabled, accepting the wildcard
source, and dropping the provenance marker each turn the suite red.
…1544)

2.25.x makes the canonical KPI card flat and horizontal, so dashboards stop
drawing a grey box inside the white card CnWidgetWrapper already draws, and
2.25.1 fixes the narrow-tile step-down that 2.25.0 shipped inert (a source-order
bug left KPI values clipped on a three-column tile).

The caret range already allowed both; only the lockfile pinned this app back.
Lockfile only.
Bumps [stylelint-config-recommended-vue](https://github.com/ota-meshi/stylelint-config-recommended-vue) from 1.6.1 to 2.0.0.
- [Release notes](https://github.com/ota-meshi/stylelint-config-recommended-vue/releases)
- [Changelog](https://github.com/ota-meshi/stylelint-config-recommended-vue/blob/main/CHANGELOG.md)
- [Commits](ota-meshi/stylelint-config-recommended-vue@v1.6.1...v2.0.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recommended-vue
  dependency-version: 2.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [postcss-html](https://github.com/ota-meshi/postcss-html) from 1.8.1 to 2.0.0.
- [Release notes](https://github.com/ota-meshi/postcss-html/releases)
- [Commits](ota-meshi/postcss-html@v1.8.1...v2.0.0)

---
updated-dependencies:
- dependency-name: postcss-html
  dependency-version: 2.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [marked](https://github.com/markedjs/marked) from 12.0.2 to 18.0.11.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Commits](markedjs/marked@v12.0.2...v18.0.11)

---
updated-dependencies:
- dependency-name: marked
  dependency-version: 18.0.11
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… to route a voorstel that has no route (#1532)

* feat(bezwaar): hold advisory committees in the decision app, and run the referral as a flow

migrate-committees-to-decidiq has been BLOCKED on two prerequisites. Both are
now in place: decidiq#874 gave GovernanceBody the four fields a
bezwaaradviescommissie needs, and decidiq's governance-body-events change gave
it the typed command seam an in-process migration must use.

This is dossiq's half.

- CommitteeDelegationService dispatches GovernanceBodyRequestedEvent, guarded
  by class_exists across BOTH namespace spellings, and fails closed when the
  decision app is absent or did not handle the command. Shaped after
  ContractDecisionDelegationService, which is this app's working example of
  commanding the decision app by event.
- MigrateCommitteesToDecidiq raises a body per existing committee and records
  the id back. It REFUSES to run when OpenRegister exposes no runAsSystem(),
  rather than falling back to a bare call like the shared trait does: a repair
  step has no session, OpenRegister then resolves the actor as Anonymous and
  refuses every write, and $output->warning() does not fail an upgrade — so the
  fallback is exactly the silent no-op the spec forbids.
- dossiq.ensureCommittee is a flow node that does the same thing mid-run, and
  the seeded `Bezwaar advies` flow on bacAdviceRequest uses it as its first
  step: ensure the committee exists, the secretary opens deliberation, the
  chair issues the advice, each human step moving the request's status.
- governanceBodyId on bezwaaradviescommissie is the mapping record.

The roster is where the sharp edge was. `members[]` may also contain the chair,
and the seam keys a seat on (person, body) — so sending a uid twice overwrites
its first seat, and the committee silently loses its chair while every row
still looks well-formed. Officers are seated first and seen uids skipped;
testChairIsNotDemotedByAppearingInMembers pins it and goes red when the
de-duplication is removed.

Also fixed while here: DossiqFlowNodeListenerTest asserted a hard-coded count
of 17 nodes. Its sibling test's own comment warns against exactly that, and it
had already gone stale. It now derives from the fixture.

Verified: 2561 unit tests pass (1 pre-existing warning in ZgwMappingServiceTest,
present on the base tree too), PHPCS/PHPMD/PHPStan/Psalm clean on every changed
file, all 71 applicable hydra gates green, and the register fragment merge was
run through the real RegisterFragmentMerger rather than assumed.

* feat(parafering): hold parafeerroutes in the decision app, and refuse to route a voorstel that has no route

Routing a document past a sequence of officials for approval is governance, and
governance is the decision app's. Its `approval-routes` change built the target
and named this app as the consumer; `approval-route-events` built the command
seam an in-process migration must use.

`parafeerroute` has carried DEPRECATED (migrate-parafering-to-or-approval-workflow)
in its schema description for months. IT IS NOT DEPRECATED. Measured today: 15
PHP files, 7 frontend files, 4 routes, 4 test files, ~1,810 lines of engine
across six services. The archive says why — that change was "archived
prematurely; implementation not present on development" and reverted. A banner
describing an intention reads, to anyone who did not check, as a state.

This moves the ROUTE, not the runtime chain:

- ParaferingDelegationService dispatches ApprovalRouteRequestedEvent, guarded
  by class_exists across both namespace spellings, failing closed.
- MigrateParafeerroutesToDecidiq holds each local route and records the id.
- activate() resolves the route through a new ParafeerrouteDirectory, takes the
  snapshot from those steps, and sends them to the decision app naming the
  voorstel as subject — so the chain is materialised there and "all pending
  approvals across apps" becomes a question anyone can ask.

An earlier draft had activation READ the route back from the decision app.
That was wrong twice: it needed a cross-app read seam that does not exist
(ADR-066 lifted the moratorium only for render-and-read collect-events), and it
was unnecessary, because dossiq HAS the steps at activation — it just read them.

### A dead end fixed while reading activate()

When no route was configured, activate() set `routeSnapshot: []` and carried on,
writing `currentStep: 1, status: in_parafering`. Every later action then failed
`Current step not found in route snapshot` — a 400 naming the snapshot while the
fault was a route nobody configured. The voorstel was parked in parafering with
no way forward and no way back.

It now refuses, before any write. testActivateReturnsArrayWhenVoorstelFound was
passing on exactly that path and had to change with the behaviour rather than be
made to keep passing.

The decision app refusing does NOT block activation — it is an optional runtime
dependency. Whether a voorstel was mirrored is recorded on the voorstel as
`approvalRouteId` rather than only logged, because a best-effort step whose only
trace is a log line is one nobody can audit.

Out of scope, deliberately: the runtime chain. dossiq's pipeline owns a status
vocabulary, a return notification, accordering effects and mandate validation
that the other engine does not. Replacing it means reproducing all four or
losing them, which is its own change with parity tests.

Verified: 2576 unit tests pass (1 pre-existing warning in ZgwMappingServiceTest,
present on the base tree too), PHPCS/PHPMD/PHPStan/Psalm clean on every changed
file, all 71 applicable hydra gates green, and the fragment merge run through
the real RegisterFragmentMerger rather than assumed. Mutation-checked: restoring
the empty-snapshot behaviour, dropping the step-order fallback, and dropping the
step-type translation each turn the suite red.

Stacked on the committee change.

* test(bezwaar): cover the committee flow node and the migration, and fix the mapping write they exposed

CI's coverage ratchet was right: this branch added a flow node and a repair step
with no tests of their own. 21 tests now cover both.

🔴 The first one written found a real bug. `recordMapping()` and the repair step
both wrote the mapping with `$committee + ['governanceBodyId' => $bodyId]`, and
PHP's `+` on arrays KEEPS the left operand's key. A committee row that already
carried an empty `governanceBodyId` — which is every row, once the schema
declares the property — kept the empty string. The mapping was never recorded,
so every run would re-dispatch, and the local side would never show a committee
as migrated. Both sites now use array_merge.

That is exactly the shape of defect the coverage gate exists to catch: the code
looked right, the migration reported success, and nothing recorded anything.

The node's tests pin the two behaviours it exists for — it FAILS the step when
the committee cannot be raised (so a run never continues past a committee in no
shared register), and it short-circuits on an already-mapped one (so a heartbeat
does not re-dispatch on every pass). The step's tests pin that it REFUSES
without runAsSystem() rather than warning, because $output->warning() does not
fail an upgrade and the silent no-op is the failure nobody notices.

One test-harness note worth keeping: the fake object service must implement
searchObjectsBySlug(), not findAll(). The SearchesObjects trait calls the
former, and a fake with the wrong method makes the step report "could not list
committees" while the assertions blame the migration.

Verified: 2582 unit tests pass (1 pre-existing warning), PHPCS/PHPMD/PHPStan
clean on both files.

* test(parafering): cover the route directory and the migration, and fix the same mapping write

The coverage ratchet applies here too: this branch added a directory and a
repair step with no tests. 18 tests now cover both.

The repair step carried the SAME `$route + ['approvalRouteId' => …]` bug the
committee migration had — PHP's `+` keeps the left operand's key, so a row that
already declares the property (which every row does, once the schema does) kept
its empty string and the mapping was never recorded. Fixed with array_merge and
pinned: restoring the `+` turns the suite red.

BesluitvormingParafeerService::activate() was already using array_merge, so the
bug was confined to the migration.

The directory's tests are mostly about the EMPTY cases, because that is the
whole reason it was extracted: the lookup used to be inline in activate(), which
carried on with an empty step list and parked the voorstel in `in_parafering`
with nothing to travel. "No route" is now a value the caller has to handle, and
these pin that it is reported rather than papered over — including when
OpenRegister is absent, when the register is unconfigured, when the read throws,
and when the stored steps are unusable.

Verified: 2615 unit tests pass (1 pre-existing warning), PHPCS and PHPMD clean.

* test(bezwaar): cover the branches the coverage guard named

The guard reported the exact shortfall: 86.69% (254/293 statements) on the
changed files against a 92.86% base, so 39 statements were uncovered. The
coverage artifact named them line by line rather than leaving it to guesswork.

They were all real branches, not filler: the dispatcher throwing, a committee
row arriving as an ENTITY rather than an array, a read that fails, a
best-effort mapping write that fails, members given as objects with their own
external flag, a members list that is not a list, entries with no usable uid,
an unusable relation value, the node's scope predicate, and the migration's two
catch arms.

Two of them are worth their own line. The entity-shaped committee: OpenRegister
hands a row back unflattened when the read did not expand it, and reading that
as an array finds nothing and reports the committee as missing. The failed
mapping write: it must NOT fail the step, because the decision app has already
raised the body by then and failing would discard work that succeeded.

Verified: 2593 unit tests pass, PHPCS and PHPMD clean.
#1547)

* test(e2e): drive the four surfaces this round of work actually changed

Playwright against a live instance, for the things a user can see. The backend
seams stay covered by unit and mutation tests; these cover what those cannot.

- The projected workflow flows are listed, and EVERY one is disabled. Verified
  by mutation against the live database: flipping a single flow to enabled
  turns this red.
- An inspector cannot escalate an LHS intervention by posting a body that
  claims a harsher recommendation. Before the fix that returned 200 with a
  stored override.
- The LHS recommendations settings entry is gone, and its PAGE still resolves,
  so deep links do not 404.
- Activating parafering on a voorstel that cannot be routed does not answer 200.

Three things learned writing them, kept in the comments because each one is a
way an e2e goes green while proving nothing.

The menu test needed a CONTROL. Its first version asserted only the absence and
would have passed against a navigation that never rendered. It now asserts a
sibling settings entry is visible first.

The labels are the DUTCH ones the app renders. Asserting the English source
strings failed and read as a broken feature rather than a broken test.

The settings entries live in a collapsed foldout, so they are in the DOM but not
visible until it is opened — the test opens it, because that is what a user
does. Asserting DOM presence instead would have passed on a foldout that can
never be opened.

The flow assertion reads the flow store through the API rather than scraping the
list: it is about what was PROJECTED, and a rendering change should not be able
to turn it either colour.

* style(e2e): apply prettier to the changed-surfaces spec

quality / Frontend Check (format) runs prettier --check across the repo. Re-ran
the four tests against the live instance after formatting, so the reflow is
verified not to have changed what they assert.
npm ci could not resolve, so every frontend job failed at install:

  Found: postcss-html@2.0.0
    peer postcss-html@"^2.0.0" from stylelint-config-recommended-vue@2.0.0
    peer postcss-html@"^1.0.0" from stylelint-config-html@1.1.0

The stylelint packages had ended up SPLIT across two generations:
recommended-vue on v2 demanding postcss-html ^2, config-html on v1
demanding ^1. No value of postcss-html satisfies both at once.

They are a set and must share a generation. This app runs
@nextcloud/stylelint-config@3, which pins the v1 line, so all three go to
v1 together rather than dragging the v2 line in behind one package.

That split is how the fleet broke tonight: eight apps could not install,
and the majors behind it were merged one at a time, each looking harmless
on its own.

Verified: the lockfile resolves from a clean tree.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Bumps [commander](https://github.com/tj/commander.js) from 12.1.0 to 15.0.0.
- [Release notes](https://github.com/tj/commander.js/releases)
- [Changelog](https://github.com/tj/commander.js/blob/master/CHANGELOG.md)
- [Commits](tj/commander.js@v12.1.0...v15.0.0)

---
updated-dependencies:
- dependency-name: commander
  dependency-version: 15.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#1554)

The control assertion hard-coded the Dutch labels, and broke the moment the
instance served English — a test that depends on the session locale tells you
about the locale, not about the feature. Both spellings are now accepted.

That control has now earned its place twice. Its first version asserted only
the absence and would have passed against a navigation that never rendered;
its second passed only in Dutch. Neither failure was the feature.

Verified: all four tests pass against a live instance serving English, having
previously passed against the same instance serving Dutch.
…#1557)

changed-surfaces.spec.ts fails on development:

  Error: no projected flow found - run `occ dossiq:workflows:migrate-to-flows`
  1 failed, 142 passed

The spec asserts that every workflow definition appears as a disabled
projected flow. Nothing in ci-seed.sh produced those flows, and the app
does not project them on install: it is a one-shot migration, which the
failure message itself names.

What makes this worth fixing rather than retrying: the spec passed most
of the time. The flows were arriving incidentally, from whatever else had
touched the instance first, so the precondition held by accident. A test
whose setup depends on ordering fails on ordering, not on the behaviour
it claims to check -- and reports the app as broken when it is not.

Running the projection in the seed makes the precondition the seed's job.
`php occ` bare matches the other seeds in the fleet; the shared workflow
invokes this script with cwd at the Nextcloud server root. Idempotent, so
a warm instance re-projects rather than duplicating.

Verified: bash -n clean.
…1556)

dossiq's E2E job failed on development:

  no projected flow found — run `occ dossiq:workflows:migrate-to-flows --user=admin` first

changed-surfaces.spec.ts asserts the PROJECTION — that every workflow
definition has a flow, and that every one arrived DISABLED. Nothing in the
register import creates those flows, so the spec was failing on a migration
that had simply never run. The message reads as a broken projection; the
projection was never made.

The spec is right to fail rather than skip. Its own header says an e2e that
cannot tell 'absent' from 'broken' reports both as fine, so the fix belongs in
the seed, not in the assertion.

Placed after section 2 deliberately: the migration reads the workflow
definitions the register import creates, so on an empty register it would
succeed having projected nothing and the spec would still fail.

The command is idempotent and creates the flows DISABLED by design — the
definitions still drive cases, and an enabled projection would move every case
a second time on each status change. That invariant is exactly what the spec
checks, so seeding must not enable them.

Verified against the dev instance rather than assumed: --dry-run exits 0 and
reports the projections with the `dossiq:workflowTemplate:` prefix the spec
matches on. bash -n passes.
…#1561)

* refactor(dmn): consume OpenRegister's shared decision-table evaluator

openregister#3186 moved dossiq's DMN engine into OpenRegister and deliberately
left dossiq's copy in place, because deleting a working evaluator on the
strength of a new one that had not yet run its data would have been the same
mistake in the other direction. It has now run its data, so the copy goes.

Normalising namespace and class name, DecisionEngine and DecisionTableEvaluator
differ only in docblocks and the two extra hit policies. Same call signature,
same return shape. For every table dossiq ships, this is a delete.

What it fixes: the DecisionTable schema has always offered five hit policies in
its enum while the engine refused two of them, so the form offered a choice it
would not honour. All five now work.

What it does not fix, despite appearances: the seeded `Voorbeeld Name 3` table
declares PRIORITY and is tempting to cite as a broken seed repaired here. Run
through the real shared evaluator it still fails, with type_mismatch. It is
placeholder data whose input entries are the literal string "Voorbeeld
Inputentries 3" against a boolean column, and it never evaluated under either
engine. The claim is only that PRIORITY is no longer refused for being PRIORITY.

EvaluateDecisionHandlerTest built a real engine and asserted on real evaluation.
It now drives a mocked evaluator and asserts what the handler actually owns:
mapping inputs in, mapping outputs onto the case, and refusing to write when
evaluation fails. The evaluation itself is proven in openregister.

The 31 grammar tests travelled first, in openregister#3191. Had the class gone
first, that coverage would have left the fleet with the file.

* fix(zgw): assert the absence of caseType instead of reading an absent key

testListMappingsReturnsAllKeys asserted `assertNull($mappings['caseType'])`.
There is no `caseType` key: RESOURCE_KEYS is the Dutch ZGW vocabulary, and
`caseType` is the English alias for `zaaktype`. So the assertion read a missing
index, PHP warned "Undefined array key", the expression evaluated to null, and
the assertion passed on the warning rather than on the service agreeing with it.

Now it asserts what is actually true: `caseType` is not a resource key. Three
docblocks offering `'zaak', 'caseType'` as example keys are corrected to
`'zaak', 'zaaktype'`, since they are where the wrong idea came from.

Pre-existing; found while running the suite for the DMN change.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
hydra-gates v1.10.0 -> v1.10.0
nc-vue      2.25.1 -> 2.26.0

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…eObject (#1564)

* fix(dmn): carry rule priority, so PRIORITY can actually rank

dossiq#1561 made the engine accept PRIORITY. Driving it against a live instance
showed that was only half the story: the table evaluated, returned 200, and gave
the WRONG answer, picking the priority-1 rule over the priority-10 one.

Two things dropped the rank before it reached the evaluator.

The DecisionTable schema had no `priority` property on a rule, because dossiq's
old engine never implemented the policy that uses it. Added, defaulting to 0.

DecisionTableService::validateRules() rebuilds every rule from a fixed list of
four keys, so `priority` was stripped on write even once the schema allowed it.
It is now carried, and only when the author supplied it: writing a default onto
every rule of every table would put a meaningless field on tables that do not
use it.

Verified end to end: a PRIORITY table with rules ranked 1 and 10 now returns the
rule ranked 10. Neither the unit tests nor reading the code found this. The e2e
did, which is the argument for having written it.

* fix(openregister): pass the uuid as the uuid to deleteObject, in all five places

OpenRegister's signature is `deleteObject(uuid, register, schema, ...)`. Five
dossiq services called it as `deleteObject($register, $schema, $id)`, which
transposes all three arguments: the register id lands in the uuid slot and the
object's uuid lands in the schema slot. Every one of those deletes has been
failing. On a live instance the decision-table delete answered HTTP 500 with
"Did expect one result but found none" against openregister_registers, because
it was looking up a register whose id was really a schema's.

A sixth, TenantSaasService, passed `id:` as a named argument. There is no `$id`
parameter, so that raised "Unknown named parameter" and was swallowed by the
surrounding catch.

InspectionChecklistService already carries a docblock describing this exact
defect, found the same way against a live instance: "no checklist has ever been
deleted through the UI". That instance was fixed; the class never was. Swept
now, and every remaining call site verified to use named arguments.

Why no test caught it: AdvisoryBodyServiceTest's fake declared
`deleteObject(string $register, string $schema, string $id)`. The fake had been
written to agree with the CALLER rather than with OpenRegister, so it accepted
the transposed call and the test passed on it. The fake now carries the real
signature, and a new test asserts the uuid arrives in the uuid slot. It fails
when the transposed call is put back, which is the only reason to trust it.

Verified on a live instance: DELETE now answers 200 where it answered 500.

* docs(openspec): record that PRIORITY needed a follow-up to become true

The dossiq-consumes-shared-dmn change asserts "PRIORITY MUST return the matching
rule with the highest priority". When it merged, that was not satisfiable: the
schema carried no `priority` property and validateRules() stripped the field, so
a PRIORITY table returned 200 with the wrong rule.

The requirement is right and is now met. The task list should say that it took a
second change, rather than reading as though it was true on the day.

* fix(quality): format the e2e spec and tag the two changed methods

Two CI findings, both mine.

Prettier: the DMN e2e test I added was not formatted. Ran the repo's own
`format:fix`; the whole tree is clean.

gate-16 spec-coverage: `RoutingRuleService::deleteRule()` and
`DecisionTableService::validateRules()` changed without an `@spec`. Both do
implement a specified requirement, so the tags say something true rather than
silencing the gate: deleteRule carries the KCC task its class docblock already
names, validateRules the dmn-decision-tables spec its siblings all carry.
Carries the two KPI-card fixes this dashboard reads: the canonical card is flat
and horizontal (no grey box inside the white card CnWidgetWrapper already
draws), and a calendar-aligned date range follows the reader's calendar rather
than UTC — "Current month" previously showed a To of 1 September in CEST on
31 August.

The caret range already allowed it; only the lockfile pinned this app back.
Lockfile only, and npm pruned nothing.
Release: merge development into beta
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ dd343aa

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
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-31 12:32 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