840: Support a form flow as start form of a building block - #886
Conversation
An action that starts a building block ran the newest version of that building block instead of the version linked to the case, failing when that version wrote to fields the linked version does not have. The process link carries the exact, versioned process definition id, but form submission forwarded only the process definition key. Resolving a key then fell back to "highest engine version not linked to a case definition", which does not exclude building-block-owned definitions, so a newer draft version won. Carry the versioned id through to the engine for process-link starts, and derive a document's blueprint as case or building block - caseDefinitionId() is null for building block documents, which is what made the lookup degrade to "latest". Resolving by key alone now refuses building-block definitions instead of guessing a version. processDefinitionId on the start-process requests is @JsonIgnore on purpose: ProcessDocumentResource binds two of them from client JSON with FAIL_ON_UNKNOWN_PROPERTIES disabled, so a bindable field would let a caller bypass key and blueprint resolution and reach a suspended draft or another case's definition.
13.40.0 is being released, so the entry moves to the next unreleased minor. Adds the 13.41.0 directory and registers it in SUMMARY.md so GitBook renders it.
Resolving the start form threw a NullPointerException. The form flow definition was looked up through the case-definition link, but a building block's main process has no such link row: only CD: blueprints get one on deploy. Resolution now falls back to the blueprint from the process definition's version tag, which covers both case definitions and building blocks. This replaces the openTask fallback on findDefinitionByKey too, which broke as soon as two building block versions owned a form flow with the same key. The form flow instance now also carries the process definition id, so submitting starts the building block version linked to the case. A key cannot identify a version, because every building block version redeploys the same key. Finally, startProcessForDocument no longer overwrites an existing process document association. A building block's start event listener creates the building block document and points the association at it while the process starts, which made the subsequent association fail. modifyDocumentAndStartProcess already guarded against this, which is why a form.io start form did work.
Both the URL process link and the form.io submission path dereferenced the case-definition link without a null check, so they would fail the same way as the form flow start form for a building-block-owned process. They now fall back to the blueprint from the process definition's version tag. Dormant until now, because both only reach this code when neither a document nor a document definition name is supplied.
Add the release note for the building block start form, and document the three valtimoFormFlow expressions with the linking context each one belongs to. The form flow page only showed completeTask, so copying its complete example onto a start event fails at runtime with an unclear error.
|
Warning Review limit reached
Next review available in: 31 minutes Limit details: You’ve used all 1 included review currently available under your plan. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughForm-flow instances now retain the resolved process-definition ID and use it when starting cases or supporting processes. Form and URL submissions resolve document definitions from case-definition links or building-block blueprint identifiers. Process startup preserves existing process-document associations. Integration tests cover linked building-block version startup, and documentation describes form-flow completion and building-block start forms. 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…o bugfix/840-form-flow-start-form-building-block # Conflicts: # documentation/release-notes/13.x.x/13.41.0/README.md
The specification that resolves a process definition from its key alone was written out three times: in OperatonProcessService, inlined in ProcessAuthorizationService and mirrored in the helper's integration test. Move it to OperatonProcessDefinitionSpecificationHelper so all three share one definition and the test covers the real specification.
…o bugfix/840-form-flow-start-form-building-block
…ons-respect-building-block-versions # Conflicts: # documentation/release-notes/13.x.x/13.41.0/README.md
…o bugfix/840-form-flow-start-form-building-block # Conflicts: # documentation/release-notes/13.x.x/13.41.0/README.md
…-flow-start-form-building-block # Conflicts: # backend/form/src/test/kotlin/com/ritense/form/service/DefaultFormSubmissionServiceTest.kt # documentation/release-notes/13.x.x/13.41.0/README.md
…latform#886) * 819: Start the building block version linked to the case An action that starts a building block ran the newest version of that building block instead of the version linked to the case, failing when that version wrote to fields the linked version does not have. The process link carries the exact, versioned process definition id, but form submission forwarded only the process definition key. Resolving a key then fell back to "highest engine version not linked to a case definition", which does not exclude building-block-owned definitions, so a newer draft version won. Carry the versioned id through to the engine for process-link starts, and derive a document's blueprint as case or building block - caseDefinitionId() is null for building block documents, which is what made the lookup degrade to "latest". Resolving by key alone now refuses building-block definitions instead of guessing a version. processDefinitionId on the start-process requests is @JsonIgnore on purpose: ProcessDocumentResource binds two of them from client JSON with FAIL_ON_UNKNOWN_PROPERTIES disabled, so a bindable field would let a caller bypass key and blueprint resolution and reach a suspended draft or another case's definition. * 819: Move release notes to 13.41.0 13.40.0 is being released, so the entry moves to the next unreleased minor. Adds the 13.41.0 directory and registers it in SUMMARY.md so GitBook renders it. * 840: Support a form flow as start form of a building block Resolving the start form threw a NullPointerException. The form flow definition was looked up through the case-definition link, but a building block's main process has no such link row: only CD: blueprints get one on deploy. Resolution now falls back to the blueprint from the process definition's version tag, which covers both case definitions and building blocks. This replaces the openTask fallback on findDefinitionByKey too, which broke as soon as two building block versions owned a form flow with the same key. The form flow instance now also carries the process definition id, so submitting starts the building block version linked to the case. A key cannot identify a version, because every building block version redeploys the same key. Finally, startProcessForDocument no longer overwrites an existing process document association. A building block's start event listener creates the building block document and points the association at it while the process starts, which made the subsequent association fail. modifyDocumentAndStartProcess already guarded against this, which is why a form.io start form did work. * 840: Resolve the document definition name via the process blueprint Both the URL process link and the form.io submission path dereferenced the case-definition link without a null check, so they would fail the same way as the form flow start form for a building-block-owned process. They now fall back to the blueprint from the process definition's version tag. Dormant until now, because both only reach this code when neither a document nor a document definition name is supplied. * 840: Document how to complete a form flow Add the release note for the building block start form, and document the three valtimoFormFlow expressions with the linking context each one belongs to. The form flow page only showed completeTask, so copying its complete example onto a start event fails at runtime with an unclear error. * 819: Move the unlinked process specification to the specification helper The specification that resolves a process definition from its key alone was written out three times: in OperatonProcessService, inlined in ProcessAuthorizationService and mirrored in the helper's integration test. Move it to OperatonProcessDefinitionSpecificationHelper so all three share one definition and the test covers the real specification. * 840: Retarget form flow start form release notes to 13.42.0 --------- Co-authored-by: Klaas Schuijtemaker <94360980+Klaas-Ritense@users.noreply.github.com>
Describe the changes
Link to the related Github issue: generiekzaakafhandelcomponent/gzac-issues#840
Important
Depends on #879 — please review and merge that one first. This branch is built on top of
bugfix/819-actions-respect-building-block-versionsbecause it needsStartProcessForDocumentRequest.withProcessDefinitionIdfrom it.Until #879 merges, this PR's diff against
next-minoralso contains its commits. Only the three840:commits (11 files) belong to this change; the diff collapses to those once #879 is merged. Everything below concerns issue 840 only.A form flow configured as the start form of a building block's main process never worked. Starting that building block from the actions of a case failed before the form even opened. The same setup with a form.io start form did work, which is why this went unnoticed.
Three defects sit on that one path, each hidden behind the previous one.
1. Resolving the start form threw a
NullPointerException.FormFlowProcessLinkActivityHandler.getStartEventObjectresolved the form flow definition through the case-definition link, andProcessDefinitionCaseDefinitionService.findByProcessDefinitionIdends in!!. A building block's main process has noprocess_definition_case_definitionrow: it is owned by a building block, andOperatonProcessService.deployonly creates link rows forCD:blueprints — there is an explicit TODO about that atOperatonProcessService.java:622. SoGET /api/v1/process-definition/{processDefinitionId}/start-formthrew and no start form opened.Resolution now falls back to the blueprint encoded in the process definition's version tag.
OperatonProcessDefinition.getBlueprintId()already parses bothBB:andCD:tags, andFormFlowServicealready has thefindDefinitionOrNull(key, BuildingBlockDefinitionId)overload, so the fallback reuses what is there rather than adding a lookup.2. The building block version was lost on submit. The handler put only the process definition key into the form flow instance's additional properties, and
ValtimoFormFlow.startCase/startSupportingProcessbuilt a key-onlyStartProcessForDocumentRequest, so the form flow submission path never carried the version. Every building block version redeploys the same process definition key under a new engine version, so a key cannot identify a version. The instance now carriesprocessDefinitionIdand passes it on withwithProcessDefinitionId(...), so the version the start form was opened for is the version that starts.3. The process document association clashed. Not in the issue — found while writing the regression test, with the first two defects already fixed. Submitting still failed with
IllegalStateException: Process was already associated with another document.startProcessForDocumentunconditionally created a process document association, butBuildingBlockStartEventListenerhas already created the building block document, pointed the association at it and rewritten the business key while the process starts. Its siblingmodifyDocumentAndStartProcessalready guarded this withfindProcessDocumentInstance(...).isEmpty()— which is exactly why a form.io start form worked and a form flow did not. The guard is now on both.Specify the code branch location:
bugfix/840-form-flow-start-form-building-block→bugfix/819-actions-respect-building-block-versions(#879)Relevant comments:
Breaking changes
No signature, constructor or JSON contract changed; all new methods are private. One behavioural change worth calling out:
startProcessForDocumentno longer throws when the process instance is already associated with a different document, it leaves that association in place. The only thing that can create an association in that window is a listener running during the start, andmodifyDocumentAndStartProcesshas always behaved this way — so this removes a divergence rather than introducing one. The other four callers (UploadProcessService,VerzoekPluginEventListener,BezwaarAdHocFvmStartFormSubmissionHandler,ValtimoFormFlow) never relied on the exception.Documentation
New features or changes that have been introduced have been documented.
documentation/features/case/form-flow.mdgained a "Completing a form flow" section covering the threevaltimoFormFlowexpressions, the linking context each requires, theinstance.idversusstep.submissionDatadifference, and the fact thatdoc:writes to the case document rather than the building block document. The complete example is now labelled as a user-task form flow.Tests
Unit tests have been added that cover these changes
URLProcessLinkServiceTestandDefaultFormSubmissionServiceTesteach gained a case proving the blueprint fallback resolves a document definition name when there is no case-definition link.FormFlowProcessLinkActivityHandlerIntTestadditionally assertsprocessDefinitionIdis on the instance for the existing case-scoped path.Integration tests have been added that cover these changes
BuildingBlockStartFormFlowIT(new) covers the whole flow: a building-block-scoped form flow on the main process start event, the building block linked to a case, a newer building block version deployed so both share one process definition key, thengetStartEventObject→completeStep, asserting the started instance runs the linked version and that exactly oneBuildingBlockInstanceexists at that version for the case document. Its ability to catch each of the three defects was verified individually — see Relevant comments.Ran green: full backend
./gradlew test, andintegrationTestingPostgresqlforbuilding-block,form-flow,process-document,process-link-url,form,case,form-view-model,zgw:verzoekandzgw:zaken-api— the last four because they are the other callers ofstartProcessForDocumentand of the two hardened resolution paths. Checkstyle passes on the touched modules with no new warnings.Describe the testing steps
${valtimoFormFlow.startSupportingProcess(instance.id, {...})}on the last stepNullPointerExceptionin the log)Security
The Secure by Design principle has been applied to these changes
The new
processDefinitionIdis populated server-side only and never bindable from a request, as described under Relevant comments — this matters because it selects which process definition gets started for a document. The association guard is narrow: it only declines to overwrite an association created moments earlier during the same start, and the association it preserves is the correct one (a building block process's business key is its own document, not the case document).Added or changed REST API endpoints have authentication and authorization in place
No endpoints were added or changed, and no request or response DTO changed.
Valtimo access control checks have been implemented
The existing check in
ProcessLinkActivityService.getStartEventObject—RelatedEntityAuthorizationRequest(OperatonExecution, CREATE, OperatonProcessDefinition, processLink.processDefinitionId)— is unchanged. The id this change carries through to the start is that same process definition id, so the definition that is started and the definition that was authorized now agree by construction, and permission rules keyed onversionTagsee the linked building block version.One pre-existing gap is left as it was and is not made worse:
ValtimoFormFlow.startSupportingProcessstill callsstartProcessForDocumentinsiderunWithoutAuthorization, under a//TODO: PBAC START/CREATE checkthat predates this change. Authorization for this flow therefore still happens when the start form is opened rather than when it is submitted.Dependencies
Newly added dependencies do not introduce known vulnerabilities/CVE's and are in line with the Valtimo license
No dependencies were added or changed.