Repository navigation
Release: merge development into beta - #4715
Open
github-actions[bot] wants to merge 54 commits into
Open
github-actions[bot] wants to merge 54 commits into
github-actions[bot] wants to merge 54 commits into
Conversation
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
…ct-actions-2-steps
…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
…#4739) Assisted-by: ClaudeCode:claude-opus-5-5
…erOf match (#4740) * test(rbac): the membership set a $memberOf operand resolves to Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): MemberOfResolver reads the caller's membership set once per request Reads the caller's rows of a membership schema without RBAC, filters them by static where conditions and an optional validity window, and returns the values of the named property. A malformed operand is unresolvable; a failed read is the empty set. Both deny. Assisted-by: ClaudeCode:claude-opus-5-5 * test(rbac): resolveOperators turns $memberOf into $in and denies what it cannot resolve Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): MemberOfResolver::resolveOperators rewrites $memberOf to $in Keeps the operator logic in the resolver so ConditionMatcher only delegates, and splits the operand checks to stay under the class complexity limit. Assisted-by: ClaudeCode:claude-opus-5-5
…ds (#4741) * test(rbac): the membership set a $memberOf operand resolves to Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): MemberOfResolver reads the caller's membership set once per request Reads the caller's rows of a membership schema without RBAC, filters them by static where conditions and an optional validity window, and returns the values of the named property. A malformed operand is unresolvable; a failed read is the empty set. Both deny. Assisted-by: ClaudeCode:claude-opus-5-5 * test(rbac): resolveOperators turns $memberOf into $in and denies what it cannot resolve Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): MemberOfResolver::resolveOperators rewrites $memberOf to $in Keeps the operator logic in the resolver so ConditionMatcher only delegates, and splits the operand checks to stay under the class complexity limit. Assisted-by: ClaudeCode:claude-opus-5-5 * test(rbac): $memberOf on the single-object verdict and the list, count and pages The list test runs the WHERE conditions a list builds, over the real MagicRbacHandler, ConditionMatcher and MemberOfResolver, against an SQLite table of three administrations: another administration's rows are excluded, COUNT(*) is exact and the LIMIT/OFFSET pages add up to the list. Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): a $memberOf match admits by the caller's membership records ConditionMatcher::resolveDynamicValue() hands an operator object holding $memberOf to MemberOfResolver, which returns it with $in over the caller's set. Both SQL list paths and the PHP verdict resolve through here, so they read the same set. Without a resolver the property is unresolvable and denies. Two token comparisons become in_array() to keep the class under the PHPMD complexity limit. Assisted-by: ClaudeCode:claude-opus-5-5
* test(rbac): the membership set a $memberOf operand resolves to Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): MemberOfResolver reads the caller's membership set once per request Reads the caller's rows of a membership schema without RBAC, filters them by static where conditions and an optional validity window, and returns the values of the named property. A malformed operand is unresolvable; a failed read is the empty set. Both deny. Assisted-by: ClaudeCode:claude-opus-5-5 * test(rbac): resolveOperators turns $memberOf into $in and denies what it cannot resolve Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): MemberOfResolver::resolveOperators rewrites $memberOf to $in Keeps the operator logic in the resolver so ConditionMatcher only delegates, and splits the operand checks to stay under the class complexity limit. Assisted-by: ClaudeCode:claude-opus-5-5 * test(rbac): $memberOf on the single-object verdict and the list, count and pages The list test runs the WHERE conditions a list builds, over the real MagicRbacHandler, ConditionMatcher and MemberOfResolver, against an SQLite table of three administrations: another administration's rows are excluded, COUNT(*) is exact and the LIMIT/OFFSET pages add up to the list. Assisted-by: ClaudeCode:claude-opus-5-5 * feat(rbac): a $memberOf match admits by the caller's membership records ConditionMatcher::resolveDynamicValue() hands an operator object holding $memberOf to MemberOfResolver, which returns it with $in over the caller's set. Both SQL list paths and the PHP verdict resolve through here, so they read the same set. Without a resolver the property is unresolvable and denies. Two token comparisons become in_array() to keep the class under the PHPMD complexity limit. Assisted-by: ClaudeCode:claude-opus-5-5 * docs(rbac): document the $memberOf match operator Assisted-by: ClaudeCode:claude-opus-5-5
…nd one body (#4744) * feat(object-actions): declared actions carry inputs, when, surfaces and 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 * refactor(object-actions): SchemaMapper takes the object action validator 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 * fix(object-actions): a register without configuration reads as no actions Psalm NullableReturnStatement: Register::getConfiguration() may be null. Assisted-by: ClaudeCode:claude-opus-5-5
Restacks after the squash merge of #4744; conflicting hunks keep the branch's content. Assisted-by: ClaudeCode:claude-opus-5-5
Restacks after the squash merge of #4744; conflicting hunks keep the branch's content. Assisted-by: ClaudeCode:claude-opus-5-5
…st-run Assisted-by: ClaudeCode:claude-opus-5-5
Restacks after the squash merge of #4744; conflicting hunks keep the branch's content. Assisted-by: ClaudeCode:claude-opus-5-5
Restacks after the squash merge of #4744; conflicting hunks keep the branch's content. Assisted-by: ClaudeCode:claude-opus-5-5
Restacks after the squash merge of #4744; conflicting hunks keep the branch's content. Assisted-by: ClaudeCode:claude-opus-5-5
…-nodes Assisted-by: ClaudeCode:claude-opus-5-5
…story Assisted-by: ClaudeCode:claude-opus-5-5
…bulk Assisted-by: ClaudeCode:claude-opus-5-5
…teps feat(object-actions): inline steps lower to a stored flow on the one engine
…ist-run feat(object-actions): one list and one run path for actions on an object
…odes feat(object-actions): openregister.transition and openregister.link-object nodes
…istory feat(object-actions): an object's action history reads action.run and action.macro
feat(object-actions): a selection above 100 becomes an openregister:run-action bulk job
…4753) POST /api/flows/{id}/dry-run {register, schema, objectId} walks the flow through the real engine with a preview dispatcher. Steps that declare IFlowSideEffectFree (set-fields, switch, route, filter, merge, explode, end, the triggers) run, so branches are decided as in a run; every other step is described and not performed. object-write previews its fields; send-email, send-notification and send-talk-message preview their composed recipients and text (FlowMessagePreview, recipients resolved through FlowRecipientAddresses, which now owns resolveRecipients). The marking lives on an unsaved run record, the object is read with the caller's RBAC and without a read audit, and nothing is sent. Gated by the flow.update right. Assisted-by: ClaudeCode:claude-opus-5-5
A fragment's task may declare performerType: external with a party:<reference> assignee and uploadRules (required, maxFiles, maxSizeBytes, acceptedTypes). Install writes the rules as metadata.upload, where the portal task seam reads them, and raises the task as the fragment's app. The check refuses an external task that is not addressed to a party, one marked done by completedBy, upload rules on an internal task and an unknown upload rule. Assisted-by: ClaudeCode:claude-opus-5-5
The task engine keeps a checklist on a task and the task page draws it,
but ExampleSetTasks refused the field, so no example task could show
one. The check now accepts a list of {id, label, checked} items and
refuses a malformed one before anything is written.
Assisted-by: ClaudeCode:claude-opus-5-5
…its objects (#4760) A follower is a row in openregister_watchers. The loader had no part for it, so no example case could show who follows it. A fragment's watchers rows are now checked, written through WatcherMapper::subscribe, proved and taken away on remove. The Zuiddrecht cast gains Anouk Bakker, who follows dossiq's Woo case on the DqZaak board. Assisted-by: ClaudeCode:claude-opus-5-5
checklistRefusal() pushed ExampleSetTasks over phpmd's class and method complexity thresholds (53 > 50, cyclomatic 13, NPath 366), failing check:strict on development. The check moves, unchanged in behaviour, into ExampleSetTaskChecklist. Assisted-by: ClaudeCode:claude-opus-5-5
#4762) The dossiq boards name the team Woo en informatie: Pieter works in it, Anouk Bakker leads it, and it is the team on the Lindelaan Woo case. The cast gains the group, Pieter and Thomas join it, and Anouk becomes its team leader. Assisted-by: ClaudeCode:claude-opus-5-5
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.