Release: merge development into beta - #3204
Conversation
The 2.0.6-beta.20260831102212 release bumped the version on beta. Without this, development stays behind beta and the next development -> beta promotion conflicts on the version file. Version files resolve to development's side, which is the higher line, so this never moves a version backwards.
Dependabot cannot propose composer updates for this app. Its updater fails
with:
Your requirements could not be resolved to an installable set of packages.
- edgedesign/phpqa[v1.27.0, ..., v1.27.2] require ext-xsl * ->
it is missing from your system.
config.platform pins php 8.3 so composer resolves against a known PHP version,
but says nothing about extensions. edgedesign/phpqa requires ext-xsl and the
resolving environment does not have it, so the resolve fails before any bump
can be computed.
CI is unaffected, which is why this went unnoticed: composer install replays
the committed lock and never re-resolves, so the pipeline stays green while
dependabot -- which does re-resolve -- fails every time. The shared quality.yml
installs no xsl extension anywhere and never invokes phpqa.
Declaring ext-xsl beside the php pin makes resolution assume exactly what the
committed lockfile already assumes. Verified in a clean composer:2 container
without --ignore-platform-reqs: the same file fails without this line and
resolves (103 installs, lock written) with it.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
The 2.0.7 release bumped the version on main. Without this, development stays behind main and the next development -> main promotion conflicts on the version file. Version files resolve to development's side, which is the higher line, so this never moves a version backwards.
….0.7 chore(release): sync main back into development
….0.6-beta.20260831102212 chore(release): sync beta back into development
Levels this app with the fleet. 2.27.2 adds two fixes the earlier 2.27.0 pin does
not carry:
* headerless is no longer chromeless — a flat KPI card in a borderless wrapper
had no card, border or background at all;
* a stat `variant` paints from the `-text` tokens rather than the fill tokens,
which failed WCAG AA at 1.08:1 as a foreground colour. The KPI colour
cleanup converted hardcoded values to `variant`, so this is what makes those
conversions contrast-safe.
Lockfile only, and npm pruned nothing.
Quality Report — ConductionNL/openregister @
|
| 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-31 11:40 UTC
Download the full PDF report from the workflow artifacts.
Bumps [zod](https://github.com/colinhacks/zod) from 4.4.3 to 4.5.2. - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](colinhacks/zod@v4.4.3...v4.5.2) --- updated-dependencies: - dependency-name: zod dependency-version: 4.5.2 dependency-type: direct:production update-type: version-update:semver-minor ... 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>
Quality Report — ConductionNL/openregister @
|
| 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-31 12:14 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 12:46 UTC
Download the full PDF report from the workflow artifacts.
…0260831125107 chore(sync): carry beta back into development
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 13:15 UTC
Download the full PDF report from the workflow artifacts.
#3220) The override read: "apexcharts": "License is MIT - license-checker misreads logo URL as custom license" That was true once and is not true now. apexcharts stopped being MIT at 6.0.0: 4.7.0 and 5.0.0 are MIT, 6.0.0 and 7.0.0 are SEE LICENSE IN LICENSE. This app declares ^7.0.0 and locks 7.0.0, so the checker flagging it is CORRECT and the override was suppressing a true finding rather than correcting a false one -- the licence gate was reporting green on a dependency that is not open source. Nothing about the dependency changes here. apexcharts 7 stays. What changes is that the file now says what is actually true: - 6.0.0+ ship a dual-license: free under a Community License for organisations below USD 2M annual revenue, paid above it, and sublicensing under different terms is not permitted. - Conduction confirmed 2026-08-31 that it is below that threshold and so qualifies under the Community License. - The override is therefore a deliberate acceptance, dated and attributed, not a claim that the checker is wrong. The note also records the redistribution question, because the Community License binds the organisation USING the software rather than only the one shipping it, and this app is distributed through the Nextcloud App Store. An installing tenant above USD 2M would need its own commercial licence. That is flagged for a decision, not decided here. For reference: dossiq is on apexcharts 4.7.0 (MIT) with no override, and its bump to 7.0.0 was closed today on these grounds (dossiq#1527). apexcharts 5.x is the last MIT release if the fleet ever needs to return to one. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 13:48 UTC
Download the full PDF report from the workflow artifacts.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 14:34 UTC
Download the full PDF report from the workflow artifacts.
The 2.0.9 release bumped the version on main. Without this, development stays behind main and the next development -> main promotion conflicts on the version file. Version files resolve to development's side, which is the higher line, so this never moves a version backwards.
…0260831154312 chore(sync): carry beta back into development
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 16:06 UTC
Download the full PDF report from the workflow artifacts.
Dependabot cannot update these packages. Its npm updater fails with: npm error code EOVERRIDE npm error Override for <pkg> conflicts with direct dependency The package is listed BOTH as a direct dependency and as a literal overrides entry pinned to the same range. Dependabot bumps the direct dependency and does not move the override, npm rejects the mismatched pair, and the update is abandoned -- so these packages can never be updated, security advisories included. npm's self-reference is the idiom for exactly this: $<name> tells the override to follow whatever the direct dependency resolves to, instead of a range that has to be kept in lockstep by hand. This repo already uses it (see the vue entry under @vue/test-utils). Transitive consumers still get pinned to one version -- the direct dependency's -- which is what the override was there to do. What changes is that the pin tracks the dependency instead of duplicating it. Verified in a clean container: bumping the direct dependency with the literal override reproduces the EOVERRIDE above; with the self-reference the same bump resolves. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 16:45 UTC
Download the full PDF report from the workflow artifacts.
…SetupHandler (#3233) * fix(db): bind catch(Exception) to a class that exists, and drop dead SetupHandler Two findings that psalm-baseline.xml had been carrying as accepted noise. Neither is noise. 187 -> 184. lib/Db/Register.php caught `Exception` with no import, inside namespace OCA\OpenRegister\Db. PHP resolves an unqualified catch type against the CURRENT namespace, so that block was bound to OCA\OpenRegister\Db\Exception, which does not exist, and it could never catch anything. Verified rather than reasoned about: namespace Demo\Sub; try { throw new \RuntimeException("boom"); } catch (Exception $e) { ... } // no import -> NOT CAUGHT -> RuntimeException: boom Adding `use Exception;` binds it to the global class the code plainly meant. Checked the rest of lib/: ObjectsController and Repair/RenameDutchColumns also catch a bare `Exception`, but both import OCP\DB\Exception, so their catches bind to a real class and are deliberate. Psalm flagged exactly the one file that was broken. lib/Service/SettingsService.php declared a property, a constructor parameter and two docblocks for SetupHandler, a class that exists nowhere in the tree. It was written and never read, so it was dead API surface that would fatal the moment anything tried to supply it. Removed, along with the `setupHandler: null` argument in Application.php and the matching positional null in the two tests that build the service positionally. Verified with the repo own vendor/bin/psalm 5.26.1 on PHP 8.3: errors with the baseline emptied went 187 -> 184, and the regenerated baseline is green. PHPUnit was NOT run here: it needs the Nextcloud server bootstrap, which only exists inside a server checkout. * fix(tests): realign the five positional constructor calls I missed Removing the SetupHandler parameter shifted every later POSITIONAL argument, and I checked for that by reading the first line after each `new SettingsService(` and concluding the file used named arguments. It uses both. Five calls further down SettingsServiceTest.php are positional, and CI caught it as 13 TypeErrors, all the same one: Argument #14 ($appName) must be of type string, MockObject_IAppContainer given which is the shift saying exactly what it was. Verified by position this time rather than by sampling: with the parameter gone, appName is slot 14, and all seven positional call sites across the three test files now carry a string literal there. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 17:06 UTC
Download the full PDF report from the workflow artifacts.
….0.9 chore(release): sync main back into development
…etter (#3236) Two more entries the Psalm baseline was carrying. Both are real. 184 -> 180. PollLinkService had a fallback that could never run: $pollId = (int)$this->db->lastInsertId("oc_polls_polls_id_seq"); if ($pollId === 0) { // Fallback for drivers without sequence support. $pollId = (int)$this->db->lastInsertId(); } OCP\IDBConnection::lastInsertId(string $table): int takes a REQUIRED argument, so the no-arg call raises ArgumentCountError on exactly the drivers the fallback was written for. The surrounding catch (Throwable) turned that into "Failed to create poll", which blames the insert rather than the id retrieval. A zero now falls through to the explicit throw that was already there and says what actually happened. Checked the other four lastInsertId call sites. MagicMapper and DedupCollidedSchemasCommand pass a table. DbalObjectSourceProvider calls it with no argument, but on a DBAL Connection rather than IDBConnection, where the parameter is optional. Psalm flagged only the one that was wrong. CaseToken declared getToken TWICE: @method string|null getToken() @method void getToken(?string $token) <- copy-paste of the setter @method void setToken(?string $token) Psalm binds to the second, which is why two correct $row->getToken() calls read as "Too few arguments". Removed; setToken on the next line was already right. Swept lib/Db for the same slip: this was the only one of them. Verified with the repo own vendor/bin/psalm 5.26.1 on PHP 8.3: all four TooFewArguments are gone, errors with the baseline emptied went 184 -> 180, regenerated baseline is green. PHPUnit was NOT run here, it needs the Nextcloud server bootstrap that only exists inside a server checkout. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-31 17:13 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 17:28 UTC
Download the full PDF report from the workflow artifacts.
…ts (#3337) This block asserted 302/303 with Location containing '#/flow-tasks/<uuid>'. Both halves are gone: #3315 rewrote TaskController::open() to return a TemplateResponse serving the SPA shell directly, and #3270 had already moved the router off hash routing so that fragment addressed nothing. It survived both changes because the block SKIPS whenever the assignee has no VTODO-capable calendar, and CI's admin has none — 'No VTODO-supporting calendar found for user admin' shows up in the server logs. So it has been asserting a contract that stopped existing, without ever running to say so. Now asserts what open() actually does: 200, and the shell rather than an API payload or an error page. Filed as #3313 on the premise that the redirect pointed at a route that did not exist; the route (/flow-tasks/:uuid, flow-task-detail in src/main.js) exists now and the redirect is gone, so nothing is left to decide. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
resolveMergeTarget() and findByUuidFollowingMerge() were built and then called by nothing, so a merge was recorded and never followed. A user whose active organisation had been merged away kept resolving to the row that was merged: it still loads, its queries still return rows, and every one of them runs under a tenant boundary that no longer applies. Both entries into fetchActiveOrganisationFromDatabase() walk it now. The stored active UUID is the obvious one. The auto-pick is the one that matters more: a user's membership list can still name a merged-away organisation, so without the walk there the first login after a merge writes the dead UUID straight back into config and re-creates the defect. The walk is guarded on isMerged(), because the flag lives on the row that was merged away and almost no row carries it, so the common case costs no query at all. Membership is then checked against the SURVIVOR, which fails closed: a user the merge did not carry over falls through to their own organisations rather than being handed a tenant on the strength of a membership that ended. An unresolvable survivor keeps the row already in hand and logs, because a read path is the wrong place to throw over a data defect. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-02 12:24 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 12:41 UTC
Download the full PDF report from the workflow artifacts.
…outcomes (#3338) * feat(tasks): task expiry enforcement and configurable timeout/reject outcomes Harvested from integriq's HITL semantics (fleet audit, wave 2): a task can now declare onTimeout and onReject in the reserved outcome vocabulary (skip|error|dead_letter). The timer sweep gains a third bounded range scan that closes non-terminal tasks past their enforcing deadline through the existing timer-outcome path (no second scheduler); a non-enforcing expiry timer falls back to the subject task's declared onTimeout; a rejecting completion of a task declaring onReject dead_letter records the dead-letter outcome through the same mapping. Both behaviours are serialized and carried by the user-task and portal-task nodes next to expiresAt. * test(migration): cover the declared-behaviour migration The coverage guard correctly flagged the one untested addition: the migration adding on_timeout/on_reject and the expiry-scan index. Three cases: fresh table gets both columns and the index, a re-run changes nothing and returns null, an absent table is left alone. * test(mapper): cover the sweep's task-timeout scan lib/Migration is excluded from clover, so the previous commit recovered nothing; the actually-uncovered addition was TaskMapper::findDueTimeouts(). The new case pins the scan's predicates (openness, declared on_timeout, deadline order) and the floored batch limit. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 13:02 UTC
Download the full PDF report from the workflow artifacts.
… inside (#3343) its own card.⚠️ THE CAUSE IS AN INSET, NOT A COMPONENT. A card widget renders `flush` and then has its padding put BACK by the card-fit rule (`padding: 8px 14px`), so the KPI sits inside the wrapper while the WRAPPER draws the border, radius and background the user reads as "the card". The library's `--clickable:hover` rule then drew a 2px border and a drop shadow on the KPI itself, 8-14px in from the edge being hovered. Measured live on dossiq, while genuinely hovered: the tile carried a 2px rgb(0,103,158) border and its own shadow, inset 9px from the wrapper's top and 15px from its left. A non-card-fit tile sits at 1px, so its border lands on the wrapper's own edge and reads as one card. That inset is the whole difference, which is why it looked app-specific and was not. nextcloud-vue#932 moves the affordance to the wrapper. No layout change: nothing moves, one card outlines. Verified in the browser with a real pointer hover: before inner 2px rgb(0,103,158) + shadow wrapper grey, no shadow after inner transparent, no shadow wrapper rgb(0,103,158) + shadow Every app with clickable stat, gauge or delta tiles had this. This bump is one of the fleet sweep that clears it. Dependency change only: package.json and the nextcloud-vue entry in package-lock.json. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 19:24 UTC
Download the full PDF report from the workflow artifacts.
TWO holes in one line, and the second is the one the line was written to
prevent.
`FederationController::createObject()` is `#[PublicPage]`, `#[NoCSRFRequired]`,
and calls `saveObject()` with `_rbac: false` and `_multitenancy: false`, so
nothing downstream refuses what it lets through. It stripped `shareToken` and
`_route`, then pinned the organisation with:
$data['@self'] = (($data['@self'] ?? []) + ['organisation' => ...]);
PHP's `+` keeps the LEFT operand's keys. So for a caller holding a writable
share token:
1. `@self.id` SURVIVED. `saveObject()` resolves its target from the payload —
`extractUuidAndNormalizeObject()` reads `@self.id` first, then `id` — and
the write is PUT-semantic, so omitted fields are NULLED. A share that
grants the right to ADD objects also granted the right to overwrite every
existing object in the shared register/schema.
2. `@self.organisation` SURVIVED TOO, which defeats the pin. The comment on
that very line reads "a federated writer can never plant an object into
another organisation". It could.
The second was found by the first test written for the first: a pin has to
overwrite, or it is only a default. Assigned rather than merged now.
`DsarCaseController::create()` gets the same treatment. It is guarded by
`requireAuthenticated()` rather than an admin setting, and stripped only
`_route`, so a create could address an existing DSAR case.
All three new tests fail against the original code and pass against the fix.
Full suite: 19037 tests, phpcs clean.
NOT CLAIMED: end-to-end exploitability. Both paths need a valid `read-write`
share token, and this was not driven against a live federated pair.
phpmd's ElseExpression rule, on the identity strip added in the previous commit. The unset is unconditional now and the conditional only re-adds a non-empty @self, which reads better anyway. Scoped phpmd on the file: clean. The full-tree run is deliberately left to CI — it OOM-killed this host once already.
createObject() was the one changed method without one. Tagged to the same federation-scope-enforcement spec its three sibling methods in this controller already point at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t-address-an-object A federated create may not address an existing object, and the organisation pin did not hold
A per-object quality score is an assessment OF the object's data, not a fact about the thing the object describes. It had nowhere to live except the object body, which forced every schema wanting a score to declare `qualityScore` and `qualityStatus` as ordinary properties. Three things followed, and all three are wrong: - The properties appear on every form the schema drives. A case handler filing a case in dossiq was shown a "Quality score" number field to fill in, for a value the platform overwrites on save. - Removing the declaration silently DELETES the values, because the store strips what the schema does not declare. So the mistake could not be undone without data loss. - Two schemas scoring the same way had to agree on property names by convention, with nothing to enforce it. `_quality` is now a metadata column of the same kind as `_validation` and `_retention`, surfaced in the `@self` envelope as `quality` with `score`, `status` and `scoredAt`. The change is additive on purpose. The listener still writes the body properties, but ONLY where the schema actually declares them: dropping that outright would freeze the stored value of every schema that has one, and a score that silently stops updating reads exactly like a score that is simply good. A schema that has migrated declares neither, so nothing is written to its objects and the body stays clean. The migration sweeps every per-schema object table, adding the column where it is missing and skipping it where present, so re-running is a no-op. It identifies an object table by the `_uuid` column rather than by the name prefix alone; verified against a live database, where every `oc_openregister_table_%` carries `_uuid` and `_validation` and none yet carries `_quality`. Verified: 1886 unit tests in tests/Unit/Db and tests/Unit/Listener pass (1881 before, plus the 5 added here), with the same 3 pre-existing vendor warnings and 5 skips as the pristine tree. PHPCS, PHPStan clean on every changed file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-03 05:59 UTC
Download the full PDF report from the workflow artifacts.
feat(quality): the quality assessment is @self metadata, not object data
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-03 07:10 UTC
Download the full PDF report from the workflow artifacts.
* feat(flow): a user task arms its own deadline FlowTimerService::arm() had no production caller anywhere in OpenRegister. Every reference to it lived in its own unit test, and it was never resolved from the container by anything that runs. The escalation ladders, the working calendar and the SLA calculator were all built, all tested and all unreachable: a flow author could describe a deadline and nothing would ever measure it. UserTaskNode now arms a timer when it creates its task, bound to subjectType 'task' and the task's uuid. That is the same pair FlowTimerSubjectTerminalListener already cancels on, so completing or cancelling the task disarms its deadline instead of leaving a timer firing against a subject that is finished. Three properties worth stating, because they are what makes this safe to add to an engine every app shares: - A node with no `sla` arms nothing and behaves exactly as before. All 36 pre-existing tests pass untouched. - An SLA that cannot be armed fails the node rather than logging and continuing. A task carrying a declared deadline that nothing is measuring is worse than a task with no deadline, because the flow reports a term it is not keeping; for a wettelijk deadline that is a legal defect, not a logging concern. No existing flow can reach this branch. - Only keys the author actually set are forwarded, so arm()'s own defaults for purpose, legalEffect and onExpiry still apply instead of being overridden with nulls. Verified: full suite 19045 tests green; negative control (remove the arm call) fails testADeclaredSlaArmsATimerBoundToTheTask; no circular DI, since FlowTimerService -> TaskService never reaches a flow node or the registry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(flow): cover the titleless-node branch the ratchet caught The merge-base coverage guard failed the branch by 0.13%: 33 added statements with exactly one never executed. It was `$title = null`, the branch that turns an empty title into null before arming. Nothing reached it because every test in the class builds its config through a helper that always supplies a title, so the branch was dead on the coverage report and nothing would have noticed if it stopped working. It is a real case, not filler. `title` is optional on a user task, and the timer renders it in the escalation notice: '' produces a blank subject line where null lets the timer describe its subject instead. The new test passes whitespace rather than omitting the key, because a title of spaces is still no title and trim() is what makes the two the same. Negative control: removing the branch so an empty title reaches the timer as '' fails testATitlelessNodeArmsItsTimerWithNoTitle. 40 tests, 155 assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#3351) PHPUnit reported these three as risky for performing no assertions. The reason is the same in all three, and it is worse than a missing assertion: each put its only assertion inside a `catch` block that did not run. try { $this->service->extractObject(1, false); } catch (\Throwable $e) { $this->assertNotSame('Object already processed and up-to-date', $e->getMessage()); } extractObject() returns void and throws nothing on the path being tested, so the catch never fired. And the string being compared is a LOG message that never reaches an exception, so even when the catch DID run the assertion was vacuous. Both tests claim to prove that extraction was not skipped, and both pass most reliably when it is skipped. Each now asserts on the only observable there is: the log. A helper captures every message the service emits, and the tests assert the up-to-date line was NOT logged, plus a control that '[ObjectHandler] Extracting text from object' WAS. Without that control the first assertion passes just as happily when extraction never began for an unrelated reason. testExtractObjectWithNullUpdatedTimestamp also needed a real fixture. It set getLatestUpdatedTimestamp() to null, which short-circuits isSourceUpToDate() before the `?? time()` fallback is ever consulted, so it would have passed with any fallback at all, including one that made the object look permanently up-to-date and skipped extraction forever. With a stored chunk at 100 the test now distinguishes `?? time()` from `?? 0`. testExtractObjectEntityExtractionFailureLogsError now tests what its name says. It never reached the entity handler before; the mocked mappers can drive the whole extraction, so the entity pass is made to throw and the test asserts the error is logged AND that the call returns. Chunks are already persisted by then, and a failing entity pass must not throw them away. Negative controls, each applied to lib/ and reverted: - `?? time()` -> `?? 0`: test 1 fails. - entity catch rethrows: test 3 errors. - force bypass removed: test 2 fails, but only when BOTH guards go. The bypass is implemented twice (extractObject and isSourceUpToDate) and either alone suffices, so neither site is individually covered. Recorded in a comment rather than silently left for the next reader. No production code changed. The two `Undefined array key` warnings the rewritten tests exposed come from a bare mock answering [] where the one real implementation of processSourceChunks() always fills all three keys, so the mock was made to keep the contract rather than the service made defensive about a fake. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-03 08:01 UTC
Download the full PDF report from the workflow artifacts.
…3354) The suite reported 10 PHP warnings and 23 deprecations. Two of the warnings were not hygiene: they were production bugs, and both had been written into the tests as known behaviour. ORGANISATION::SETACTIVE(FALSE) NEVER DEACTIVATED ANYTHING It called parent::setActive(active: $value) with a NAMED argument. Entity has no such method — it is reached through __call(array $args), where a named argument lands under its name, so Entity::setter() read an undefined $args[0] and the value never arrived. OrganisationController has an endpoint whose whole job is deactivating an organisation, and it could not work. OrganisationTest asserted assertTrue() on a value that should have been false and called it "the current actual behavior". ENDPOINTLOG::SETRESPONSE() STORED NULL ON EVERY CALL LOG Same root cause, same shape. setStatusCode and setStatusMessage were positional and worked; the response payload was dropped on every endpoint call ever logged. EndpointServiceTest said "setResponse uses named arg in source code (known issue), so response may be null. We verify it was attempted" — and then verified nothing. Both are fixed and both now assert the correct behaviour. Reintroducing either bug reddens its test; that was checked, not assumed. I swept for the rest of the class rather than stopping at two: 94 Entity subclasses, 686 magic setter names, 256 named-argument call sites in lib/. Every other match is legitimate — $objectService->setRegister() is a real service method, ->setObject(type:, id:) is OCP's INotification, and the last two candidates were the regex matching self:: in a class constant. The class is bounded at these two. THE REST - SettingsController: `while ($row !== false)` never terminates if fetch() answers null, which it can. Two other loops in the same file already used is_array(); this one did not. - BsnFormat: str_pad() was handed a null, deprecated in 8.1 and a TypeError in 9. Cast once up front; an array/object is now refused explicitly, which is the contract str_pad() used to enforce further down by accident. - SaveObject: a schema with a null slug cannot be found BY slug, and feeding the null to strtolower()/strcasecmp() is deprecated. Skipped instead. - Four test files: mocks that answered [] or null where the single real implementation always fills the contract (rechainAll, processSourceChunks, handlePreValidationCascading). The mocks were made to keep the contract rather than the services made defensive about a fake. RechainAuditTrail also gained the tombstone-count assertion its message never had. - Dynamic properties declared; ReflectionProperty::setValue() given its explicit null object argument at 14 sites. Suite: 19046 tests. Warnings 10 -> 0, deprecations 23 -> 2, risky 3 -> 0 (the risky three landed separately in #3351). The last two deprecations are ZipArchive::open() on an empty fixture file. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…them (#3340) * feat(organisations): adopt a leaf app's organisations without losing them Several apps grew their own organisation schema before OR's Organisation carried the fields they needed. The slug is global per organisation, so those copies collide: SchemaMapper::find() matches LOWER(slug) across every app and hands back whichever row it reaches first. Task 1 added the columns, which made reuse possible and moved nobody's rows. This moves them, under two rules that were learned rather than chosen. The uuid is the idempotency key and is preserved. dossiq's migrate-partners arrived at the same rule and wrote down why: a leaf row is free to carry no slug at all, and two rows sharing a name are routine, so a name-derived key skips the second as already migrated and silently merges two distinct legal entities. Where the same legal entity already exists under a different uuid the rows are not collapsed into one. The adopted row is created and pointed at the existing one through mergedInto, so both uuids keep resolving and the merge is a fact recorded on a row rather than data thrown away. Matching runs on OIN, then RSIN, then KVK, normalised for punctuation because the same OIN is typed with and without spaces and dots, and never on a name. The lowest id is canonical so a repeated run picks the same survivor, and a candidate that was itself merged away loses to a live one. Properties Organisation has no column for are named before the write. OpenRegister discards an undeclared property and answers 200 with the object, so an adoption that loses fields is otherwise indistinguishable from one that did not. Dry-run by default. Verified on the dev instance end to end: adoption, a second run adopting nothing, a merge matched across punctuation, and the negative control where clearing the shared OIN stops the merge being reported. * docs(organisations): record what the leaf-side measurement found The plan for task 5.2 was to map stackiq's fields onto Organisation where they fit and rehome the rest. Reading the entity rather than assuming, none of the three candidates fit. contacts is linked Nextcloud Contacts app data, not a list of contact people. children is not stored at all: the setter says it is loaded on demand for API responses, so a written value is dropped and recomputed. type has a closed vocabulary and a collaboration SUBtype is a different axis from it. So all nine stackiq properties belong on a stackiq-owned schema and none on Organisation, which is a different change from the one that was planned. Also records the site counts that decide the sequencing: 22 for opencatalogi, most of them stored references that keep resolving, against 235 for stackiq, which treats the slug as a first-class object type. * test(organisations): drive the adopt command end to end The coverage guard was right again: the pure rules were pinned and the command that runs them was not, so execute(), adoptRow(), reportUndeclared() and existingOrganisations() shipped uncovered. Ten cases through CommandTester, and the ones that matter are the refusals. Without --register the command says so rather than reading whatever it can find. The default is a dry run, because the alternative default is a command that writes to every organisation on the instance the first time somebody types its name to see what it does. A row with no uuid is skipped rather than adopted, since without an idempotency key it would be duplicated on every run. One case is worth its own line: two rows in the SAME run sharing a legal identifier now merge, because the candidate set grows as rows are adopted. That path had no coverage and is exactly where a by-value parameter would have silently done nothing. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ❌ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ❌ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-03 08:21 UTC
Download the full PDF report from the workflow artifacts.
… no object (#3346) Apps grew their own document object to hold a publication window over an attached file. Measuring opencatalogi's, every other property it carries already has a home: filename and mime type are the file, description and labels are the OR-side metadata row, the owning publication is the folder the file lives in, and the file's text is already extracted into openregister_chunks and searchable. The window was the only real gap. publishFile() is a boolean, so an attachment could not be depublished on a date independently of the record it belongs to, which is what a WOO bijlage needs, and that alone forced a whole object type into existence. The second reason is stronger. ContentSearchHandler already resolves a file chunk to its owning object through findOwningObjectUuid(), so a keyword hit inside a file attached to a publication resolves straight to the publication. The schema widening in opencatalogi #1391 exists only because the attachment is a separate object outside the catalog's schema scope. Files on publications would have made that class of bug impossible. The window is a rule rather than a comparison because each null in it means something different: no publication date is never published and must not fall back to the creation time, a future one is not yet, and no depublication date is no end date rather than an end date in the past. Start inclusive, end exclusive, so a zero-length window publishes nothing. A depublication date is written onto the public share's expiration, which Nextcloud already honours. An OR-side flag alone would leave a public URL that still serves the file, and a URL that still works is not a depublication. Two repairs on the way. formatFile() reported the creation time under 'published', so every file that had ever existed looked published and 'not published' was unrepresentable; the creation time is kept under 'created', where it is true. And FileMapper declared a @phpstan-type alias named File describing a filecache ROW, which shadowed the entity of the same name in every docblock in that file: a method annotated @return File read as an array shape. Renaming it to FilecacheRow and naming the entity in the generic removed 12 phpstan baseline entries. Verified on the dev instance: the migration applies and is a no-op on re-run, and all four window states behave correctly round-tripped through the database, not only in memory. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ❌ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| build | ❌ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ⏭️ | ❌ | |||
| npm | ❌ | ❌ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-03 08:25 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-03 08:43 UTC
Download the full PDF report from the workflow artifacts.
…forever (#3358) A UserTask's completion signal was REFUSED on the acceptance rig (the assignee group did not exist at signal time), and the suspended run's 30-minute heartbeat then fired, re-suspended, and never advanced: resume_at rolled 08:07 -> 08:37 -> ... while the task sat `completed` and the group had long been created. The heartbeat exists precisely to recover a missed wake; it recovered nothing. The node was never the problem. UserTaskNode has always re-read its task on re-entry and applied the outcome when terminal. What wedged is upstream: persistResult() dropped EVERY parked node's resume slot whenever a pass ended anything but `suspended`. FlowResumeState::storableWhen(suspended:) read NOT-SUSPENDED as "nothing left to continue from", which conflates it with TERMINAL -- and a pass legitimately ends `queued` while a node parked in an EARLIER pass still waits: the in-request advance of a sibling branch finalises `queued` whenever other enabled work remains, and a refused claim does the same. The parked node then lost the uuid of the task it was waiting on. Its next wake found an empty slot and -- correctly, by its own idempotency guard -- created a NEW task. From that moment the ORIGINAL task's completion could never address the node's slot, its signal was refused against the new slot's recorded assignee (the refusal observed on the rig), every heartbeat re-read the new open task and re-suspended, and a duplicate task sat in somebody's inbox. Slots now survive every pass end the run can still advance from, and drop only on a terminal one. A recovered delivery is recorded on the task's audit as `heartbeat-recovered`, attributed to the task's completedBy, so the trail no longer ends at the refusal. The symmetric cases need no new mechanism, and the change says so. A task completed while the run was not yet suspended (the race) loses its signal -- signal() refuses a non-suspended run -- and the node then parks on a non-null heartbeat, so the next wake re-reads it; the race costs at most one heartbeat period. A task concluded by TaskSequenceService reaches its terminal state on the same row the re-read observes, so it is covered with no sequence-specific handling. No second delivery mechanism is added: the wake, the re-read and the outcome application are the paths that already existed. Proven red before the fix: FlowHeartbeatRecoveryTest drives the real engine, dispatcher, node, stream walk, claims and commit path over in-memory mappers, so the pass-to-pass persistence that loses the slot is exercised exactly as the worker exercises it. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…chor (#3360) The mapper could answer "what happened to THIS object" three ways and "has this approval ever run" not at all. Every existing finder constrains anchor_object_uuid, so an aggregate over a template had no anchor to pass. buildiq is the caller that needed it. Its automation dry-run panel used to read the newest ApprovalStep on a chain; when #3302 retired that surface there was no replacement for the aggregate, so the panel degraded to reporting nothing at all (buildiq#651). This restores it. One row off the existing template_id index, ordered like its siblings and limited in the QUERY rather than trimmed in PHP. Tested with a negative control, not just a green run: adding an anchor predicate and dropping the limit makes the new test fail with "Failed asserting that an array does not contain 'anchor_object_uuid'". A finder that quietly constrained the anchor would answer a different question than the caller asked, which is the one thing this method must not do. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ❌ | ||||
| phpcs | ✅ | ||||
| phpmd | ⏭️ | ||||
| psalm | ❌ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ❌ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-03 10:41 UTC
Download the full PDF report from the workflow artifacts.
…3362) The changed-files coverage ratchet on #3358 was right, and it was pointing at something real rather than at a percentage. Of the 32 statements that change added, 30 were the body of `FlowTaskBridge::recordHeartbeatRecovery()` -- and every test that exercised the recovery MOCKED FlowTaskBridge, because in those tests the nodes are the unit. So the method that writes the recovery's audit trail had no execution coverage at all: the audit entry, its attribution, and the catch that makes it best-effort were asserted nowhere. That matters more than the percentage does. The guarded signal seam records a refusal; this entry is the other half of that trail, and a silent regression in it would make a recovered answer read as one that vanished. Three tests through the REAL bridge, in the suite that already builds one: - the entry is recorded as `heartbeat-recovered`, attributed to the task's completedBy, with a reason naming the run whose signal never arrived; - an audit write that THROWS is swallowed, because the recovery is the node applying the outcome and letting the failure out would abort the very walk that was un-wedging the run; - an ending nobody answered (terminated, expired -- `completedBy` is null on exactly those) records no actor rather than a guessed one. And one test for the symmetric case the change documented but left unpinned: a completion that RACED the suspension. `signal()` refuses a run that is not suspended, so that wake is lost with nothing to retry it; the test asserts the refusal, asserts the run parks on a non-null heartbeat, and asserts the next wake recovers it. That is the whole basis for deciding the race needs no new mechanism, and it is now falsifiable. Every one of the four was checked by mutation -- breaking the action name, the attribution, the catch, or the recovery call itself turns each red. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| test-l10n-parity | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 543/543 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-03 11:07 UTC
Download the full PDF report from the workflow artifacts.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.