An external interaction and a user task are, at their core, the same thing: a parked token waiting for someone to respond, after which the engine resumes with an outcome. They had separate presentation layers. This unifies them so a single interaction plugin drives either doorway.
What this delivers
- Two doorways, one plugin. An interaction runs behind the public capability-token dispatcher (bearer) or as an
interaction_taskinbox task (identity), via a continuation handle the doorway resolves. - Webform. A start-on-submit handler, collect/review/edit modes, feedback prefill, and an option to inject the whole submission value map into a process variable.
- Assignment plugin type lifted to the kernel, so neither doorway depends on the other; adds
users_variable/roles_variable. - Bearer-interaction notification split into a channel-neutral event (
orchestra_interaction_notification) and a default mail handler (orchestra_interaction_mail). - orchestra_webform_example: a submission-validation workflow (poster, validator, processor) with a modify loop, in both doorways.
- Flow conditions route on a bare scalar outcome as its own
.result.
Issue fork orchestra-3607035
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
mably commentedComment #3
mably commentedLocking the model after discussion (this rewrites the earlier sketch). Goal: an interaction plugin works transparently regardless of who performs the step, an anonymous link-holder, an authenticated user, or an assigned operator, with no per-plugin special casing.
Core insight
Resuming a parked step is a single action. What varies is only who is authorized to do it. The bearer capability token and the task assignment check are the same kind of thing: two answers to one question, may this actor resume this step. A token is not tied to anonymity, an authenticated user can present one too. So the model is one resume, pluggable authorization, and an optional work-item lifecycle.
One resume
Resume the parked step with an outcome or result, write it to the node result variable, call
WorkflowEngine::signal(), advance. This is the shared core that bothresumeWithResult()andWorkItemManager::complete()already bottom out in.Pluggable authorization
Interchangeable strategies that all answer may this actor resume this step:
userCanAct().They are interchangeable in mechanism but NOT in security semantics: a bearer token is transferable and leakable (whoever holds the link can act), assignment is identity-bound (only the named users can act). Authorization is therefore a conscious per-step choice.
The work item is orthogonal lifecycle, not authorization
A work item adds claim and reassign, the inbox listing, and outcome validation against the configured set. It is the tracking and lifecycle layer, driven when a step is assignment-gated. A user task is therefore assignment authorization plus the work-item lifecycle; the resume itself is the same shared action.
The seam in code
InteractionContextalready abstracts the step URLs behindInteractionUrlsInterface. Generalize that into a gateway that is primarily an authorization strategy over the shared resume, owning:returnUrl(),signalUrl($outcome),branchReturnUrl()), already present;resume($outcome, $result)that performs the shared resume, and when assignment-gated also drives the work-item lifecycle;continuationHandle(), an opaque token an off-site surface such as a webform carries to return. In the token strategy it is the capability token (self authorizing); in the session or assignment strategies it is a plain reference re-checked against the authenticated user on return.Plugin
respond()code never changes: it only talks to the context. The webform stops embedding the capability token and embedscontinuationHandle();OrchestraResumeHandlerresolves the handle to a gateway and callsresume()instead ofresumeWithResult()directly. That makes every plugin, including the webform, transparent across all authorization modes.Consequence for ExternalInteraction
The current
ExternalInteractionfeature is misnamed: it is not external-specific, it is just an interaction on a parked step. Who performs it is the gateway concern layered on. Concretely the dispatcher (InteractionController) gains a tokenless, permission-gated authenticated path alongside the capability-token one, both resuming through the engine. Renaming the feature toInteractionis worth a small separate issue.Phases
InteractionControllerandReviewHandlerFormto resume through it. No behavior change.continuationHandle()andOrchestraResumeHandlerthrough the gateway, so the form and webform interactions are transparent across modes too.Invariant
Do not expose a bearer path on a step gated by assignment, or the point of assigning it is undone. A leaked link must be inert under the session or assignment strategies: the gateway re-checks the authenticated user, and the handle is not a credential.
Tests
Open decisions
ExternalInteractiontoInteraction(separate issue?).usertask type or a new node type (recommend a feature, for composition).InteractionUrlsInterface.Comment #7
mably commentedComment #9
mably commentedComment #11
mably commentedFiled #3607206: Let the workflow own a webform interaction's post-submit behavior (chain steps) as a follow-up for the one remaining cell, the bearer plus view combination: a view-mode webform interaction is not yet rendered read-only through the public dispatcher (following the link redirects back to the collect form). Read-only view mode already works under the task doorway and the bearer doorway works for collect, so this is a coverage gap rather than a regression.