Skip to content

Release: merge development into beta - #4715

Open
github-actions[bot] wants to merge 100 commits into
betafrom
development
Open

github-actions[bot] wants to merge 100 commits into
betafrom
development

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

rubenvdlinde and others added 30 commits October 20, 2026 11:32
An example set seeds the records a demo needs, but a task lives in the
task engine, not in a register, so no fragment could put one on its
objects. A fragment may now declare tasks: a key and the task's fields
(title, description, objectUuid, state, assignee, candidateGroups,
priority, dueAt, kind, and completedAt and completedBy for a finished
one), placeholders allowed.

The check refuses an unknown field, a task without a title or object,
an assignee or completer the set does not create or adopt, a state the
engine does not map and a date that is not one. Install writes each
task once through TaskService::import (the trusted path, so a task may
be born completed), keyed example-set:<app>/<key>, records its uuid
and reports a recorded task that is gone as missing. Remove deletes
the tasks before the objects they are on.

Assisted-by: ClaudeCode:claude-opus-5-5
…0261020094809

chore(sync): carry beta back into development
A macro bound its flow by uuid, but a flow an app ships in
x-openregister-flows gets a uuid minted per installation, so no app
could ship a macro. A binding may now name a flow the same schema
declares: the save checks the declaration (a manual trigger is
required), and a run resolves the name to the stored flow by the
import's identity, name and declaring schema.

Assisted-by: ClaudeCode:claude-opus-5-5
…trail (#4722)

Suspending, reactivating, ending, retaining or archiving an organisation,
or editing its name, description or slug, left no entry on the audit
trail. Each change now writes one sealed entry keyed by the
organisation's uuid in object_uuid, with the acting user:
organisation.status_changed from TenantLifecycleService and
organisation.updated from the organisation update endpoint. Fail-soft,
like the settings and grant auditors. dossiq's organisation History tab
reads them through GET /api/audit-trails?object_uuid=.

Assisted-by: ClaudeCode:claude-opus-5-5
* feat(flow): a macro run's audit entry lists what it changed

The action.macro audit entry named the action, the flow and the run, but
not what the run did, so a page could not say "2 changes: term
suspended, message sent" (dossiq DqZaakAssistent) without opening the
run. The entry's context now carries `changes`: each completed step that
wrote an object, sent a message or created a task, by its own label and
kind, in run order.

Assisted-by: ClaudeCode:claude-opus-5-5

* feat(flow): name a macro's changes by the node's label

A run's log names a step by its node id ("set-term"), which is not what
a page should print. A flow node may carry a `label` written for people
("termijn opgeschort"); the changes on the action.macro audit entry use
it, and fall back to the id.

Assisted-by: ClaudeCode:claude-opus-5-5

* feat(flow): a labelled app step counts as a macro change

OpenRegister cannot tell what another app's node type does, so a
dossiq step such as suspending the term never counted as a change. A
completed step whose node carries a `label` now counts as kind `step`.

Assisted-by: ClaudeCode:claude-opus-5-5

* fix(flow): macro run changes pass phpmd and the controller test

CI on #4721: phpmd flagged the static MacroRunChanges::fromLog call in
the controller and its cyclomatic complexity of 11, and the controller
test still expected the macro audit context without `changes`. The
changes are now an instance method split into labelsOf() and
changeOf(), the resolver composes them (changesOf()), and the test
expects `changes: []`.

Assisted-by: ClaudeCode:claude-opus-5-5
)

PUT/PATCH /api/organisations/{uuid} let any member rename an
organisation or change its description; only a change of who holds
grant_manage asked for more. Ruben decided members only read it. The
update now checks isOrganisationAdmin (a Nextcloud administrator or the
organisation's owner) before anything is written, and answers a member
403. The grant_manage-only guard could no longer be reached and goes.

Assisted-by: ClaudeCode:claude-opus-5-5
…4724)

* feat(example-sets): a fragment may declare saved views

Dossiq's search page draws a saved search with a count alert, and a
saved view lives in the view table, not a register. A fragment may now
declare views (name, description, owner, isPublic, query, presentation,
alert) for the set's own accounts: checked before anything is written,
written once through ViewService::create (which validates presentation
and alert), recorded, proved and deleted on remove.

Tasks and views share one path: ExampleSetEngineRows holds the check
and install loops, ExampleSetEngineKinds runs every kind for the
installer and the remover, and a kind this OpenRegister cannot write
refuses its rows instead of skipping them.

The cast gains Fatma Kaya, regisseur jeugdteam (group Sociaal domein),
the handler of dossiq's jeugdwet example case, and Sanne de Vries lives
at Parkstraat 24, as DqContact draws her.

Assisted-by: ClaudeCode:claude-opus-5-5

* fix(example-sets): keep Sanne de Vries at Lindelaan 14

portaliq's fragment writes her own cases at that address (Dakkapel
Lindelaan 14), so moving her to Parkstraat 24 for the contact board
would contradict them. That move waits for a decision.

Assisted-by: ClaudeCode:claude-opus-5-5

* test(example-sets): declare the engine-row classes the example-set tests run

Every example-set test now builds the installer and remover with the
tasks and views writers, so under beStrictAboutCoverageMetadata each
test that did not list them was risky and its coverage was dropped,
which read as 0% on the changed files in the coverage guard.

Assisted-by: ClaudeCode:claude-opus-5-5
* feat(example-sets): a fragment may declare saved views

Dossiq's search page draws a saved search with a count alert, and a
saved view lives in the view table, not a register. A fragment may now
declare views (name, description, owner, isPublic, query, presentation,
alert) for the set's own accounts: checked before anything is written,
written once through ViewService::create (which validates presentation
and alert), recorded, proved and deleted on remove.

Tasks and views share one path: ExampleSetEngineRows holds the check
and install loops, ExampleSetEngineKinds runs every kind for the
installer and the remover, and a kind this OpenRegister cannot write
refuses its rows instead of skipping them.

The cast gains Fatma Kaya, regisseur jeugdteam (group Sociaal domein),
the handler of dossiq's jeugdwet example case, and Sanne de Vries lives
at Parkstraat 24, as DqContact draws her.

Assisted-by: ClaudeCode:claude-opus-5-5

* fix(example-sets): keep Sanne de Vries at Lindelaan 14

portaliq's fragment writes her own cases at that address (Dakkapel
Lindelaan 14), so moving her to Parkstraat 24 for the contact board
would contradict them. That move waits for a decision.

Assisted-by: ClaudeCode:claude-opus-5-5

* test(example-sets): declare the engine-row classes the example-set tests run

Every example-set test now builds the installer and remover with the
tasks and views writers, so under beStrictAboutCoverageMetadata each
test that did not list them was risky and its coverage was dropped,
which read as 0% on the changed files in the coverage guard.

Assisted-by: ClaudeCode:claude-opus-5-5

* feat(example-sets): Pieter Jansen joins the Zuiddrecht cast

Dossiq's boards draw the cases list as Pieter Jansen, a handler in
Vergunningen, and its saved-view boards open the row menu of a view he
owns. A fragment may only give a view to an account the set creates or
adopts, so he joins the cast with the account id `pieter`, the one the
dossiq examples already name as a participant.

Assisted-by: ClaudeCode:claude-opus-5-5
…#4737)

Spec-only change object-actions-declared-and-run. A declared action
(x-openregister-action) gains inputs as JSON Schema, a precondition,
surfaces and one body (transition, flow or inline steps lowered to the
flow engine). One list endpoint per object merges declared actions with
lifecycle transitions; one run path validates inputs, checks the right
and runs sync or async; every run writes one action.run audit entry.
Includes the migration path for dossiq, pipelinq and learniq verbs and
six open questions with recommendations.

Assisted-by: ClaudeCode:claude-opus-5-5
…nd one body

Generalises the macro binding into ObjectActionDefinition (tasks 1.1-1.5):
label, icon, surfaces, inputs (JSON Schema), when (JSONLogic), confirm,
mode, next, elevate and exactly one body (transition, flow or steps).
A named flow is a body on its own; macro: true stays accepted.

Schema save refuses two bodies, an undeclared transition, non-object
inputs, invalid JSONLogic, unknown surfaces, bulk with @object defaults,
async transitions, async writeOnly inputs, wildcard or ungranted elevate,
and elevate saved by a non-administrator. Register-level actions are
inherited at read time; a schema entry replaces the inherited one whole
and an inherited entry never elevates.

Assisted-by: ClaudeCode:claude-opus-5-5
…engine

Tasks 2.4 and the run-context half of 2.3. FlowDefinitionBuilder::lowerSteps()
turns an action's steps into a straight line: manual trigger, the new
openregister.action-inputs step, the steps, an end node carrying next.
ObjectActionStepsFlow stores that document once per content hash as an
inert system flow (disabled, ownerless) and publishes it, so a steps run
is an ordinary run readable at /api/flow-runs/{uuid}.

Schema save refuses a step whose node is unknown, whose config the node
refuses, or that branches, waits or bounds the flow, and says to bind a
flow instead. writeOnly inputs reach the steps through a request-scoped
holder and never enter the stored run context.

Assisted-by: ClaudeCode:claude-opus-5-5
Tasks 2.1-2.3, 2.5, 2.8, 2.9 and 4.1.

- GET /api/objects/{register}/{schema}/{id}/actions lists the runnable
  declared actions the caller holds the right to (inherited ones too)
  and every lifecycle transition, each with availability and reason;
  a transition a declared action names is listed once under its key.
- GET /api/object-actions/{register}/{schema} lists the bulk-surface
  actions, right checked at schema level.
- POST .../actions/{action} takes {inputs}: 404 unknown, 403 no right,
  409 action-precondition-unmet, 422 invalid inputs (per field) or a
  refusing body. transition goes through TransitionEngine::transition(),
  flow through FlowService::run(), steps through the new
  FlowService::runForAction(). Async answers 202 queued, a suspended sync
  run 202 suspended.
- A selection only runs actions offered on bulk.
- Every run writes one action.run audit entry with the inputs digest;
  writeOnly inputs are left out of the digest and the stored context.
- available-actions keeps its shape and gains a Deprecation header and
  a Link to the new list.

Assisted-by: ClaudeCode:claude-opus-5-5
…bject nodes

Tasks 3.1 and 3.2. openregister.transition takes {action, data?, object?}
and moves the item's object through TransitionEngine::transition() as the
run's identity; a refused move fails the step with the engine's message.

openregister.link-object either creates an object from {register,
schema, data} and writes its uuid into {relation} on the item's object
(appended to a list), or runs the item schema's declared
x-openregister-handoff through HandoffService. An absent implementer
fails the step with not-available instead of succeeding silently.

Both declare their BPMN kind and palette category.

Assisted-by: ClaudeCode:claude-opus-5-5
… action.macro

Task 4.2. GET /api/objects/{register}/{schema}/{id}/actions/history lists
the object's action.run entries and the older action.macro entries as one
kind, newest first, each with its run link, its changes and its outcome.
The caller needs read on the object. Older macro entries are read as they
were written; the hash chain forbids rewriting them.

Assisted-by: ClaudeCode:claude-opus-5-5
…un-action bulk job

Task 2.6. POST /api/object-actions/{register}/{schema}/{action} with more
than 100 ids now answers 202 with a previewed bulk job of the new bulk
action openregister:run-action and the link to commit it, instead of a
400. The preview asks each member the run's own checks (action, right,
when, inputs) through ObjectActionRunner::check(); the commit runs the
action as the person who started the job.

Assisted-by: ClaudeCode:claude-opus-5-5
…tor alone

The object action validator already asks the macro validator, so the
mapper needs one optional collaborator, not two; this keeps its
constructor at nine parameters (phpmd ExcessiveParameterList). Without
a validator wired the shape refusals of both still fire, and the save
warns when runnable actions were saved unverified.

Assisted-by: ClaudeCode:claude-opus-5-5
…ions

Psalm NullableReturnStatement: Register::getConfiguration() may be null.

Assisted-by: ClaudeCode:claude-opus-5-5
…ookup

RecentLensLookupCallSitesTest: an internal find must say whether it is a
lookup; this one reads the object only to write the relation back.

Assisted-by: ClaudeCode:claude-opus-5-5
rubenvdlinde and others added 30 commits October 22, 2026 16:16
…oost

feat(example-sets): Buurtvereniging Oost and its chair Marja Verbeek join the cast
feat(example-sets): a fragment may put parties on its objects
The export endpoint dropped every `_`-prefixed parameter as request
plumbing, so `export?format=json&_ids[]=a&_ids[]=b` exported every row the
caller could read. `_ids`, as a list or a comma-separated string, now
narrows the search behind the export to those objects; RBAC and
multitenancy still apply to each. Without `_ids` nothing changes.

A list page can then export its selected rows (dossiq's case type list,
"Selectie exporteren als JSON").

openspec change export-honours-selected-ids.

Assisted-by: ClaudeCode:claude-opus-5-5
RecentLensLookupCallSitesTest fails on development: FlowDryRunService::dryRun
read the object with _audit false as the ninth positional argument, which
the scan cannot see. The call now names its arguments, so the decision is
visible and the base is green again.

Assisted-by: ClaudeCode:claude-opus-5-5
A fragment's view can now carry `sharedWith`: a list of { user | group,
mode } with mode read (may use) or write (may update), handed to
ViewService::create. A user share must name an account the set creates or
adopts, as an owner must; a share names exactly one target; an unknown
mode is refused. dossiq's Zuiddrecht fragment uses it for Pieter's
"Wacht op inwoner" (board DqFilterDelen: shared with Woo en informatie
and Anouk Bakker).

Assisted-by: ClaudeCode:claude-opus-5-5
…-lookup

fix(flows): the dry run's object read says it is a lookup
…et-notes-and-read-states

# Conflicts:
#	lib/Command/ExampleSetInstallCommand.php
#	lib/Service/ExampleSet/ExampleSetCatalogue.php
#	lib/Service/ExampleSet/ExampleSetEngineKinds.php
#	lib/Service/ExampleSet/ExampleSetInstaller.php
#	lib/Service/ExampleSet/ExampleSetRecord.php
#	lib/Service/ExampleSet/ExampleSetRemover.php
#	tests/Unit/Service/ExampleSet/ExampleSetInstallerTest.php
#	tests/Unit/Service/ExampleSet/ExampleSetTestCase.php
…-read-states

feat(example-sets): notes and read states
Boards DqContact, PqKlant and PqBriefOpstellen draw her BRP first names as
"Sanne Johanna"; the cast had only her given name. The cast gains
`firstNames`, which her BRP row's voornamen now quotes. Her given name
stays "Sanne" (the portal greets her by it) and her initials stay "S."
(the case lists draw "S. de Vries"). Her birth date stays 14 March 1988:
MijnGegevens, PqKlant, PqBriefOpstellen and PtGegevensPdf draw 1988, only
DqContact draws 1986.

Assisted-by: ClaudeCode:claude-opus-5-5
…ept-objects

fix(example-sets): a later install attaches the files a kept object gained
…ion-quota-and-members

feat(example-sets): a cast organisation may declare its quota and its members
…and-organisation

feat(example-sets): a fragment may give a task its engine key and organisation
feat(example-sets): a saved view may declare its shares
…-first-names

feat(example-sets): Sanne de Vries's first names are Sanne Johanna
LqCfPrivacyverzoek opens a data-subject request from OpenRegister's
shared data-subject-requests register, which learniq reads through the
objects API, and the demo instance holds none. The Zuiddrecht cast file
now writes one: a parent's erasure request for a former pupil, in
progress, handled by Pieter, with its statutory term still running.

Assisted-by: ClaudeCode:claude-opus-5-5
…ure-request

feat(example-sets): a data-subject request in the Zuiddrecht set
The archiving page (#4796) and every board-look widget since 2.72 need a
current library. The one break is in the unit tests: the library's formatters
now reach @nextcloud/initial-state, pure ESM that jest does not transform, so
the Connections page spec mocks it the way it already mocks @nextcloud/auth.

Assisted-by: ClaudeCode:claude-opus-5-5
chore(deps): @conduction/nextcloud-vue 2.72.0 to ^2.129.0
Bumps [smalot/pdfparser](https://github.com/smalot/pdfparser) from 2.13.0 to 2.13.1.
- [Release notes](https://github.com/smalot/pdfparser/releases)
- [Commits](smalot/pdfparser@v2.13.0...v2.13.1)

---
updated-dependencies:
- dependency-name: smalot/pdfparser
  dependency-version: 2.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [nextcloud/ocp](https://github.com/nextcloud-deps/ocp) from 35.0.1 to 35.0.2.
- [Commits](nextcloud-deps/ocp@v35.0.1...v35.0.2)

---
updated-dependencies:
- dependency-name: nextcloud/ocp
  dependency-version: 35.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

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 18.0.14 to 18.1.1.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Commits](markedjs/marked@v18.0.14...v18.1.1)

---
updated-dependencies:
- dependency-name: marked
  dependency-version: 18.1.1
  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>
…4729)

Bumps [jest-environment-jsdom](https://github.com/jestjs/jest/tree/HEAD/packages/jest-environment-jsdom) from 30.5.1 to 30.5.2.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.5.2/packages/jest-environment-jsdom)

---
updated-dependencies:
- dependency-name: jest-environment-jsdom
  dependency-version: 30.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [theodo-group/llphant](https://github.com/LLPhant/LLPhant) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/LLPhant/LLPhant/releases)
- [Commits](LLPhant/LLPhant@1.0.6...1.0.7)

---
updated-dependencies:
- dependency-name: theodo-group/llphant
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@vue/test-utils](https://github.com/vuejs/test-utils) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/vuejs/test-utils/releases)
- [Commits](vuejs/test-utils@v2.5.0...v2.5.1)

---
updated-dependencies:
- dependency-name: "@vue/test-utils"
  dependency-version: 2.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

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

* feat(tasks): the task subject context carries the subject's identifier

A task row's subject now carries identifier, the subject object's own human
number (a case's "2026-0074"), read in the same batch as its title, so a task
list can show it under the title (dossiq board DqTaken).

Assisted-by: ClaudeCode:claude-opus-5-5

* fix(tasks): a single task read carries its subject context

GET /api/flow-tasks/{uuid} and the verb responses built the row with no subject;
they now use TaskInboxService::enrich, the inbox row with its subject, so a
task page can name its case (dossiq DqTaak "bij zaak 2026-0074").

Assisted-by: ClaudeCode:claude-opus-5-5
…bjects (#4800)

* feat(organisations): an organisation quota limits its users and its objects

dossiq's DqTenant draws "Zaken 1.240 van 5.000" and "Gebruikers 24 van
50". An organisation gains userQuota, objectQuota and objectQuotaSchema
(the slug of the schema the object quota counts; none counts every
schema), nullable as the other quotas. quota answers users, objects and
objectsSchema; usage.objects counts the organisation's live objects of
that schema, only with an object quota, null when it could not be
counted. The update endpoint and example-set quotas accept them; the
Zuiddrecht set gives Gemeente Waterland 50 users and 5,000 cases.
Generic: OpenRegister knows no cases, the app names the schema.

Assisted-by: ClaudeCode:claude-opus-5-5

* chore(organisations): move the app version so the quota migration runs on upgrade

Assisted-by: ClaudeCode:claude-opus-5-5

* fix(organisations): the quota migration hands back the schema on every path

check:strict: SchemaReuseHygieneTest requires every changeSchema() return
to be $schema, so the shared snapshot is kept.

Assisted-by: ClaudeCode:claude-opus-5-5
* feat(example-sets): a fragment may ship flows

An example set could not ship a flow, so the apps' flow pages showed
whatever flow was lying around (board DqFlow). A fragment's flows part
declares flows of its app with their steps' places on the canvas:
checked first, stored switched off and published like a shipped flow,
proved on reinstall and removed with the set. FlowService::deleteStored
holds the delete cascade for a caller that already decided.

Assisted-by: ClaudeCode:claude-opus-5-5

* fix(flows): say why FlowService has a public method more than phpmd's ten

check:strict's phpmd counted deleteStored() as the eleventh public method.
It is delete()'s cascade without the caller check, for an example set
removing the flow it wrote.

Assisted-by: ClaudeCode:claude-opus-5-5
…lug once (#4809)

dossiq's user settings listed "Mijn dagelijkse werkoverzicht" twice: the
instance holds two workDigest schema rows for dossiq, an older 1.0.0 that
an import left behind (in no register, no objects) and the current 1.0.1.
Preferences are keyed by the slug, so the second row could only repeat the
first. The list now reads one schema per owning app and slug, the highest
version, then the highest id.

Assisted-by: ClaudeCode:claude-opus-5-5
…#4810)

A read audit entry says who opened a record; on a family or health case
that is itself personal data. Administrators always see read entries;
anyone else only when in a group the new setting audit.readEventsVisibleTo
lists (JSON, empty by default, security relevant). ObjectHistoryForReaders
drops read entries before paging and answers readEventsVisible; the admin
history answers true; the readable feed leaves them out. Fails closed on a
missing user, an unreadable setting or a failing group lookup.

Assisted-by: ClaudeCode:claude-opus-5-5
#4811)

{{view:<key>.uuid}} stands for the uuid of a saved view the same fragment
declares, and a fragment's user preferences are now written after its
views. The check accepts the placeholder with a stand-in. dossiq's
Zuiddrecht set uses it to put Pieter's "Termijn deze week" in his menu
(board DqFilterInMenu).

Assisted-by: ClaudeCode:claude-opus-5-5

This branch has not been deployed

No deployments
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