feat(flow): persist runs — the critical path for five workstreams (#2076)#2078
Merged
Conversation
… them The critical path of the parity programme (#2076). Five workstreams were waiting on this one deferral: Wait, sub-flows, run history, retry, and the Nextcloud Flow bridge executing anything at all. - `FlowRun` + mapper + `openregister_flow_runs`, storing the marking, the items, the context and the log. - `FlowRunMarkingStore` puts the marking on the RUN rather than the subject. Two runs over one object hold two independent markings, which is exactly why Symfony's subject-property stores do not fit. - `FlowSuspension` — a step pauses the run by throwing. An exception rather than a return value on purpose: a node returns ITEMS, and smuggling "please suspend" into that return would force every node author to know about a magic item shape, with a forgetful node silently continuing instead. - The engine catches suspension BEFORE the generic Throwable handler, so a step's `onError` policy never sees a pause — `continue` would otherwise skip straight past a Wait, which is the opposite of waiting. The marking is not advanced, so the run resumes ON the step that asked to wait. - `FlowRunService` — queue / execute / resume. Resuming uses the stored items, never a re-seed, or everything earlier steps produced would be discarded. A terminal run is never re-executed; retry creates a new one. - `FlowRunWorker` — per-minute job draining the queue, waking due runs, and pruning terminal ones. Retention ships here rather than later: runs grow without bound and this instance has already been taken down once by a file nobody was pruning. Only a run with a `resumeAt` is woken on a timer. One waiting on a signal — a child run, a webhook — has none, and waking it on a clock would run it before what it waits for arrives. 67 tests green, phpcs clean.
Contributor
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ❌ | ✅ 173/173 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-07-24 14:42 UTC
Download the full PDF report from the workflow artifacts.
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.
Proposal: or-flow-runs
Summary
Make a flow run outlive the request that started it.
Why
FlowEnginetakes aMarkingStoreInterfaceand the engine change (#2064)deferred persisting the marking. That single deferral is what stands between us
and five separate workstreams:
Five things, one dependency. It is built first, not alongside.
What Changes
FlowRun+FlowRunMapper+openregister_flow_runs. A run stores itsmarking, its items, its context and its log.
FlowRunMarkingStore— aMarkingStoreInterfacebacked by the run row, sothe marking survives the request.
FlowSuspension— thrown by a step that wants the run paused. An exception,not a return value: a node returns ITEMS, and smuggling "please suspend" into
that return would mean every node author had to know about a magic item
shape, with a forgetful node silently continuing.
FlowEngine::STATUS_SUSPENDED, caught before the genericThrowablehandler so a step's
onErrorpolicy never sees a pause.continuewouldotherwise skip straight past a Wait, which is the opposite of waiting.
FlowRunService— queue, execute, resume, persist.FlowRunWorker— a per-minute job that starts queued runs, resumes due ones,and prunes old ones.
Design decisions
ON that transition, re-entering the step that asked to wait. Advancing would
skip it.
subject would discard everything earlier steps produced.
the old one would repeat every side effect it already performed.
and grow without bound; this instance has already been taken down once by a
file nobody was pruning. Default 30 days,
0disables.Out of scope (this change)
the flow-document store. Until then the worker leaves a claimed run for the
next pass rather than failing it, so no run is lost between the two changes.
Verification
67 unit tests green in
tests/Unit/Service/Flow(11 new),phpcsclean on every new file. The tests cover the properties that actually matter: a suspended run keeps its place in the graph, resuming carries the stored items rather than re-seeding,resumeAtis cleared once no longer suspended, the log accumulates across a pause, a terminal run is never re-executed, and a malformed flow fails the run instead of leaving itrunning.Unblocks #2067 (Wait, sub-flows), #2070 (history, retry), #2068 (triggers), and lets the existing Nextcloud Flow bridge execute instead of running inline.
🤖 Generated with Claude Code