Skip to content

Commit 17df32f

Browse files
authored
Merge pull request #3204 from ConductionNL/development
Release: merge development into beta
2 parents d82e5f4 + de1bed0 commit 17df32f

1,118 files changed

Lines changed: 147264 additions & 28528 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

--help

Whitespace-only changes.

.githooks/pre-commit

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,20 @@ if git diff --cached --name-only | grep -qE "^openspec/(specs/|features\.overlay
4343
fi
4444
fi
4545

46+
# A NEW MIGRATION NEEDS A VERSION BUMP, and this is the cheapest place to be
47+
# told. Nextcloud reads an app's migration files only when appinfo/info.xml's
48+
# <version> is greater than the installed_version it recorded, so a migration
49+
# committed without a bump runs on no existing instance and says nothing about
50+
# it. CI (merge-hygiene) is the enforcement; this only tells you earlier.
51+
#
52+
# WARN ONLY, like everything else in this hook: a commit is a fine moment to
53+
# learn about it and a bad moment to be blocked, and the bump is often the last
54+
# thing you write. The gate on the PR is what actually holds the line.
55+
if git diff --cached --name-only --diff-filter=A | grep -q "^lib/Migration/.*\.php$"; then
56+
if ! php scripts/check-migration-version-bump.php >/dev/null 2>&1; then
57+
echo "pre-commit: WARNING — this change adds a migration without moving <version> in appinfo/info.xml." >&2
58+
echo " Run: php scripts/check-migration-version-bump.php" >&2
59+
fi
60+
fi
61+
4662
exit 0

.github/workflows/api-test-coverage.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,36 @@ jobs:
131131
# than 40 lines later where it reads as a dependency-install failure.
132132
docker exec nextcloud php /usr/local/bin/composer --version
133133
134+
- name: Provide git inside the container
135+
# Same shape as the composer step above, and for the same reason: the
136+
# unit suite runs INSIDE the nextcloud container, and that image ships
137+
# no git.
138+
#
139+
# MigrationVersionBumpCheckTest builds a small repository per case and
140+
# runs scripts/check-migration-version-bump.php over it, because a test
141+
# that inspects THIS repository can only report whatever today's branch
142+
# happens to look like. With no git the fixtures are never created, so
143+
# all six repository cases failed with
144+
#
145+
# /tmp/or-migration-gate-256a137d7fd3 is not a git repository
146+
#
147+
# which reads as a defect in the gate rather than an absent binary.
148+
# Measured 2026-09-06: this job had failed on all eight development
149+
# pushes since the gate landed in #3451, and `git --version` in the
150+
# container answers `command not found`. With git present the class
151+
# passes 8/8.
152+
#
153+
# apt rather than `docker cp` of the runner's binary: git is not a
154+
# single file like the composer phar, and a copied binary would leave
155+
# its shared libraries and helper programs behind.
156+
run: |
157+
set -e
158+
docker exec -u root nextcloud bash -lc \
159+
'apt-get update -qq && apt-get install -y -qq --no-install-recommends git'
160+
# Prove it is usable HERE, as the user the suite runs as, rather than
161+
# 40 lines later where it reads as a broken gate.
162+
docker exec -u www-data nextcloud git --version
163+
134164
- name: PHPUnit unit suite (in-container, HARD GATE)
135165
# The unit suite requires the full Nextcloud runtime — the OCP stubs
136166
# reference Doctrine\DBAL\* and OC\* internals that only resolve once

.github/workflows/code-quality.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,31 @@ permissions:
172172

173173
jobs:
174174
quality:
175-
if: github.event_name != 'push' || github.event.created != true
175+
# SECOND CLAUSE: skip the run a STANDING release PR triggers.
176+
#
177+
# `Release: merge development into beta` is permanently open with
178+
# `head_ref: development`, so every push to development fires this
179+
# workflow TWICE on the SAME sha — once for `push`, once for that PR's
180+
# `pull_request` event. The concurrency block above deliberately gives
181+
# them separate lanes so the push run is not cancelled, which is right,
182+
# and the consequence is that both run to completion.
183+
#
184+
# The PR run is the redundant one, not the push run: its head sha IS
185+
# development's, which the push run already decided, and the push run
186+
# carries jobs the PR run does not (Coverage Baseline Check, SBOM,
187+
# Features Extract). Measured 2026-09-03: runs 33747123932 (push) and
188+
# 33747129312 (pull_request), same sha b8eb72e2, four seconds apart.
189+
#
190+
# 20 of the fleet's 21 apps carry `beta` in `pull_request.branches` and
191+
# so pay this on every development push. Removing `beta` from that list
192+
# would also work and is worse: the `release/v*` and `sync/main-to-beta`
193+
# PRs genuinely need their run, and they target `beta` too.
194+
#
195+
# A `development -> main` promotion PR is skipped by the same clause, for
196+
# the same reason and just as correctly.
197+
if: >-
198+
(github.event_name != 'push' || github.event.created != true)
199+
&& (github.event_name != 'pull_request' || github.head_ref != 'development')
176200
uses: ConductionNL/.github/.github/workflows/quality.yml@main
177201
with:
178202
app-name: openregister

.github/workflows/merge-hygiene.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ jobs:
3333
name: Conflict markers and PHP syntax
3434
runs-on: ubuntu-latest
3535
steps:
36+
# FULL HISTORY, deliberately. The migration/version check below compares
37+
# this branch against the merge base with `development`, and a depth-1
38+
# checkout has no merge base to find — it would report "no verdict" on
39+
# every run, which is the silent skip the check exists to remove.
3640
- uses: actions/checkout@v4
41+
with:
42+
fetch-depth: 0
3743

3844
# Conflict markers, anywhere in the tree we author. A marker means a merge
3945
# was committed half-finished; every downstream signal from that commit is
@@ -69,6 +75,30 @@ jobs:
6975
php-version: '8.3'
7076
coverage: none
7177

78+
# A MIGRATION THAT SHIPS WITHOUT A VERSION BUMP REACHES NOBODY.
79+
#
80+
# Nextcloud runs an app's migrations only when appinfo/info.xml's
81+
# <version> is greater than the installed_version it recorded. Equal
82+
# versions mean `occ upgrade` answers "No upgrade required.", exits 0, and
83+
# reads none of the migration files — no log line, no failure, and the
84+
# feature that needed the table is simply absent.
85+
#
86+
# Measured on a throwaway NC 34 rig 2026-09-05: a migration added with the
87+
# version left alone did not run, was not recorded, and
88+
# `occ migrations:status openregister` reported "Pending Migrations: None"
89+
# while showing 204 executed of 205 available. Bumping only <version>, with
90+
# the code byte-identical, ran it. The version string is the whole gate.
91+
#
92+
# It lives here rather than in Code Quality because it needs the base ref
93+
# and takes milliseconds — the same reason everything else in this file is
94+
# here.
95+
- name: A new migration moves the app version
96+
run: |
97+
set -euo pipefail
98+
BASE="${{ github.event.pull_request.base.ref || 'development' }}"
99+
git fetch --no-tags --prune origin "$BASE"
100+
MIGRATION_VERSION_BASE_REF="origin/$BASE" php scripts/check-migration-version-bump.php
101+
72102
# Every PHP file parses. A conflict marker is caught above, but so is any
73103
# other way a file can be committed unparseable — and this is the check
74104
# that would have failed within seconds of the merge landing.

.license-overrides.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"@fortawesome/free-solid-svg-icons": "License is (CC-BY-4.0 AND MIT) — both are approved open-source licenses, compound AND expression not parsed by checker",
33
"smalot/pdfparser": "License is LGPL-3.0 — equivalent to LGPL-3.0-only which is on the allowlist, SPDX identifier variation not recognized by checker",
4-
"apexcharts": "License is MIT — license-checker misreads logo URL as custom license, see https://github.com/apexcharts/apexcharts.js/blob/main/LICENSE",
4+
"apexcharts": "NOT MIT. The last MIT release is 5.0.0 -- 5.1.0 onward is the ApexCharts dual-license (Community free only below USD 2M annual revenue; sublicensing under different terms not permitted). This app declares ^7.0.0. Conduction confirmed 2026-08-31 it is below USD 2M, so it qualifies for the Community tier. 🔴 UNRESOLVED: an OEM/Redistribution licence is separately required when embedding ApexCharts in a product used by other people, and is only waived for STATIC charts users cannot interact with -- RegisterDetail.vue and SchemaDetails.vue both set toolbar.show and zoom.enabled, and this app ships to third parties via the Nextcloud App Store. Escalated to the maintainers; do not read this override as a clearance for redistribution.",
55
"dompdf/dompdf": "License is LGPL-2.1 — equivalent to LGPL-2.1-or-later which is on the allowlist; configured hermetically (isRemoteEnabled=false / isPhpEnabled=false) at the single instantiation site PdfReportWriter:69"
66
}

appinfo/info.xml

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Open Register drijft apps zoals OpenCatalogi, Procest, Pipelinq en Software Cata
4040
4141
Vrij en open source onder de EUPL-licentie.
4242
]]></description>
43-
<version>2.0.12</version>
43+
<version>2.0.15-unstable.20260905143606</version>
4444
<licence>EUPL-1.2</licence>
4545
<author mail="info@conduction.nl" homepage="https://www.conduction.nl/">Conduction</author>
4646
<namespace>OpenRegister</namespace>
@@ -129,27 +129,26 @@ Vrij en open source onder de EUPL-licentie.
129129
<job>OCA\OpenRegister\BackgroundJob\TransferCheckJob</job>
130130
<job>OCA\OpenRegister\BackgroundJob\SyncConfigurationsJob</job>
131131
<job>OCA\OpenRegister\BackgroundJob\SyncDataJob</job>
132-
<job>OCA\OpenRegister\BackgroundJob\ScheduledWorkflowJob</job>
133132
<job>OCA\OpenRegister\BackgroundJob\ScheduledNotificationJob</job>
134-
<job>OCA\OpenRegister\BackgroundJob\ActionScheduleJob</job>
135133
<job>OCA\OpenRegister\BackgroundJob\TenantDeprovisionJob</job>
136134
<job>OCA\OpenRegister\BackgroundJob\TenantPurgeJob</job>
137135
<job>OCA\OpenRegister\BackgroundJob\TenantUsageSyncJob</job>
138-
<job>OCA\OpenRegister\BackgroundJob\ExecutionHistoryCleanupJob</job>
139136
<job>OCA\OpenRegister\BackgroundJob\FlowRunRetentionJob</job>
140137
<job>OCA\OpenRegister\BackgroundJob\AuditSealJob</job>
141138
<job>OCA\OpenRegister\BackgroundJob\AvgRetentionJob</job>
142139
<job>OCA\OpenRegister\BackgroundJob\DsarRetentionSweepJob</job>
143140
<job>OCA\OpenRegister\BackgroundJob\ReportRenderJob</job>
144141
<job>OCA\OpenRegister\BackgroundJob\NotificationQueueFlushJob</job>
142+
<job>OCA\OpenRegister\BackgroundJob\TaskScheduledNotificationJob</job>
145143
<job>OCA\OpenRegister\BackgroundJob\ArchivalRetentionTask</job>
146144
<job>OCA\OpenRegister\BackgroundJob\FlowRunWorker</job>
147145
<job>OCA\OpenRegister\BackgroundJob\FlowScheduleWorker</job>
146+
<job>OCA\OpenRegister\BackgroundJob\FlowTimerWorker</job>
148147
<job>OCA\OpenRegister\BackgroundJob\HandoffQueueDrainJob</job>
149148
<job>OCA\OpenRegister\BackgroundJob\TemporalCalculationSweepJob</job>
150149
<job>OCA\OpenRegister\BackgroundJob\DsarDpiaDetectionJob</job>
151-
<job>OCA\OpenRegister\BackgroundJob\ScheduleReconcilerJob</job>
152150
<job>OCA\OpenRegister\BackgroundJob\DbalIntrospectionJob</job>
151+
<job>OCA\OpenRegister\BackgroundJob\OAuth2TokenRefreshJob</job>
153152
<job>OCA\OpenRegister\BackgroundJob\ScheduledReportJob</job>
154153
<job>OCA\OpenRegister\BackgroundJob\GroupReconcilerJob</job>
155154
</background-jobs>
@@ -219,6 +218,27 @@ Vrij en open source onder de EUPL-licentie.
219218
Copies the register-authored ones across, disabled, leaving the
220219
register rows in place so the step stays reversible. -->
221220
<step>OCA\OpenRegister\Repair\MigrateRegisterFlowsToTable</step>
221+
<!-- CLEANS UP AFTER THE VERSION OF THE STEP ABOVE THAT DID NOT SCOPE
222+
ITS READ. It asked findAll() for register `flows` / schema `flow`
223+
at the TOP LEVEL of the config; ObjectService reads that pair
224+
from `filters` and nowhere else, so both keys were inert and the
225+
read ran against whatever register/schema the SHARED service was
226+
still carrying. saveObject() sets that context and never restores
227+
it, and ImportCredentialBrokerRegister saves two example objects
228+
through it four steps earlier — so the migration copied both
229+
`brokeredcredential` examples into openregister_flows: no nodes,
230+
no edges, no trigger, owner `__system__`.
231+
232+
Post-migration ONLY. A fresh install runs the fixed migration,
233+
which cannot produce these rows, so listing this under <install>
234+
would be a guaranteed no-op.
235+
236+
Removes a row ONLY on the full conjunction: openregister-owned,
237+
disabled, no nodes, no edges, no trigger/cron, never dispatched
238+
(lastRun* null AND zero run rows), AND its uuid still resolves to
239+
a register object whose schema is not `flow`. Anything ambiguous
240+
is REPORTED by uuid, not deleted. Idempotent. -->
241+
<step>OCA\OpenRegister\Repair\PurgePhantomMigratedFlows</step>
222242
<step>OCA\OpenRegister\Repair\ImportTrustConfigurationRegister</step>
223243
<step>OCA\OpenRegister\Repair\SeedVocabularyRegister</step>
224244
<step>OCA\OpenRegister\Repair\RegisterOpenRegisterWithDoriath</step>
@@ -240,6 +260,42 @@ Vrij en open source onder de EUPL-licentie.
240260
Post-migration, because it needs the flow_* columns the schema
241261
migration adds. -->
242262
<step>OCA\OpenRegister\Repair\RechainAuditTrailForFlowAttribution</step>
263+
<!-- Task fixtures (flow-task-entity, ADR-001): the five seed
264+
groups from the change's design.md, idempotent on uuid.
265+
OPT-IN: runs only when app config openregister/seed_demo_tasks
266+
is true, so a production instance never receives demo rows;
267+
a demo instance and the test environment switch it on.
268+
Post-migration because the step needs the openregister_tasks
269+
tables its own change's migration creates. -->
270+
<step>OCA\OpenRegister\Repair\SeedTaskFixtures</step>
271+
<!-- Business timers (flow-business-timers, ADR-001): the flow-timers
272+
register with its working-calendar and escalation-ladder schemas
273+
and seeded defaults (nl-national, nl-termijn-default), idempotent
274+
with force: false so administrator edits survive. Then the
275+
invariant check, which COUNTS orphaned or inconsistent timers and
276+
reports them rather than cancelling them. Post-migration because
277+
both need the openregister_flow_timers tables. -->
278+
<step>OCA\OpenRegister\Repair\SeedFlowTimerRegister</step>
279+
<step>OCA\OpenRegister\Repair\CheckFlowTimerInvariants</step>
280+
<!-- Approval consolidation (flow-approval-consolidation): moves
281+
every approval chain, step set and in-flight decision onto
282+
task sequences, verifies the reconciliation and FAILS LOUDLY
283+
on any mismatch. Idempotent on the reconciliation columns.
284+
Post-migration because it needs openregister_task_sequences
285+
and the sequence columns on openregister_tasks. The legacy
286+
tables are kept; rollback is the
287+
openregister:approval:rollback-to-steps command. -->
288+
<step>OCA\OpenRegister\Repair\MigrateApprovalChainsToTasks</step>
289+
<step>OCA\OpenRegister\Repair\SeedCaseFixtures</step>
290+
<!-- Business timers (flow-business-timers, ADR-001): the flow-timers
291+
register with its working-calendar and escalation-ladder schemas
292+
and seeded defaults (nl-national, nl-termijn-default), idempotent
293+
with force: false so administrator edits survive. Then the
294+
invariant check, which COUNTS orphaned or inconsistent timers and
295+
reports them rather than cancelling them. Post-migration because
296+
both need the openregister_flow_timers tables. -->
297+
<step>OCA\OpenRegister\Repair\SeedFlowTimerRegister</step>
298+
<step>OCA\OpenRegister\Repair\CheckFlowTimerInvariants</step>
243299
</post-migration>
244300
<install>
245301
<step>OCA\OpenRegister\Repair\ReconcileDeclaredBackgroundJobs</step>
@@ -281,6 +337,7 @@ Vrij en open source onder de EUPL-licentie.
281337
<step>OCA\OpenRegister\Repair\SeedVocabularyRegister</step>
282338
<step>OCA\OpenRegister\Repair\RegisterOpenRegisterWithDoriath</step>
283339
<step>OCA\OpenRegister\Repair\SeedZgwZakenMigrationPack</step>
340+
<step>OCA\OpenRegister\Repair\SeedFlowTimerRegister</step>
284341
</install>
285342
</repair-steps>
286343

@@ -295,6 +352,9 @@ Vrij en open source onder de EUPL-licentie.
295352
<!-- App-rename repair: re-points registers and schemas at a new owning
296353
app id. Only a DB connection, so the DI graph stays light. -->
297354
<command>OCA\OpenRegister\Command\MigrateSchemaApplicationCommand</command>
355+
<!-- The approval consolidation's reverse repair: an operator-run
356+
rollback tool, deliberately NOT a repair step. -->
357+
<command>OCA\OpenRegister\Command\RollbackApprovalMigrationCommand</command>
298358
<command>OCA\OpenRegister\Command\RechainAuditTrailCommand</command>
299359
<!-- Read-only report: which RBAC groups this instance declares, and whether
300360
anyone belongs to them. Provisioning guarantees a declared group EXISTS;
@@ -310,8 +370,14 @@ Vrij en open source onder de EUPL-licentie.
310370
<!-- field-level-object-encryption: encrypt existing plaintext values of a newly-flagged property. -->
311371
<command>OCA\OpenRegister\Command\EncryptFieldCommand</command>
312372
<command>OCA\OpenRegister\Command\DedupeRegistersCommand</command>
373+
<command>OCA\OpenRegister\Command\AdoptLeafOrganisationsCommand</command>
374+
<command>OCA\OpenRegister\Command\PruneRetiredSchemasCommand</command>
313375
<command>OCA\OpenRegister\Command\RelinkRegisterSchemasCommand</command>
314376
<command>OCA\OpenRegister\Command\ReconcileMagicTablesCommand</command>
377+
<!-- The sanctioned administrative purge. Every HTTP delete route refuses an
378+
archival record; this is the one path that can still destroy one, and it
379+
needs shell access plus an explicit force flag. -->
380+
<command>OCA\OpenRegister\Command\PurgeObjectCommand</command>
315381
<command>OCA\OpenRegister\Command\DedupeConfigurationsCommand</command>
316382
<command>OCA\OpenRegister\Command\ResolverListCommand</command>
317383
<command>OCA\OpenRegister\Command\TimeReconcileCommand</command>
@@ -363,4 +429,14 @@ Vrij en open source onder de EUPL-licentie.
363429
<contactsmenu>
364430
<provider>OCA\OpenRegister\Contacts\ContactsMenuProvider</provider>
365431
</contactsmenu>
432+
<!-- The in-band write-back hook for projected task VTODOs
433+
(flow-task-inbox-projections, design D-6). apps/dav's PluginManager
434+
loads it into the Sabre server; it acts only on VTODOs carrying
435+
X-OPENREGISTER-TASK and refuses an unauthorized edit with a DAV 403
436+
before the client records it. -->
437+
<sabre>
438+
<plugins>
439+
<plugin>OCA\OpenRegister\Dav\TaskVtodoWriteBackPlugin</plugin>
440+
</plugins>
441+
</sabre>
366442
</info>

0 commit comments

Comments
 (0)