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_task inbox 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

Command icon 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

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review
mably’s picture

Locking 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 both resumeWithResult() and WorkItemManager::complete() already bottom out in.

Pluggable authorization

Interchangeable strategies that all answer may this actor resume this step:

  • Capability token: the actor holds a valid signed capability in the link. Works for anonymous and authenticated alike.
  • Session permission: an authenticated user, gated by permission or role, no token.
  • Assignment: an authenticated user who is the assignee or candidate, gated by 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

InteractionContext already abstracts the step URLs behind InteractionUrlsInterface. Generalize that into a gateway that is primarily an authorization strategy over the shared resume, owning:

  • the step URLs (returnUrl(), signalUrl($outcome), branchReturnUrl()), already present;
  • an authorize check for the current request;
  • a resume($outcome, $result) that performs the shared resume, and when assignment-gated also drives the work-item lifecycle;
  • a 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 embeds continuationHandle(); OrchestraResumeHandler resolves the handle to a gateway and calls resume() instead of resumeWithResult() directly. That makes every plugin, including the webform, transparent across all authorization modes.

Consequence for ExternalInteraction

The current ExternalInteraction feature 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 to Interaction is worth a small separate issue.

Phases

  • Phase A: introduce the gateway abstraction over the shared resume; wrap the current capability-token behavior as the token strategy; refactor InteractionController and ReviewHandlerForm to resume through it. No behavior change.
  • Phase B: add the session-permission and assignment strategies; the inbox completion controller renders the configured interaction plugin under the assignment strategy and drives the work-item lifecycle; surface the choice in the node editor.
  • Phase C: route the webform off-site surface through continuationHandle() and OrchestraResumeHandler through 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

  • Kernel: the same interaction plugin, resolved under each strategy, resumes the step and writes the outcome to the result variable; the assignment strategy also updates the work item. Existing capability-token tests stay green.
  • Functional: an authenticated user completes an interaction with no token (session strategy); an assignee completes a task-gated one while a non-candidate gets a 403; a leaked session or assignment link does not act without an authorized session; the capability link still works anonymously.

Open decisions

  • Rename ExternalInteraction to Interaction (separate issue?).
  • Whether the assignment strategy is a feature on the user task type or a new node type (recommend a feature, for composition).
  • Naming of the gateway abstraction and whether it replaces or extends InteractionUrlsInterface.

mably’s picture

Title: Let user tasks use an interaction plugin as their completion UI » Unify external interactions and user tasks behind one interaction plugin
Issue summary: View changes

  • mably committed 43530c6c on 1.x
    feat: #3607035 Unify external interactions and user tasks behind one...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mably’s picture

Filed #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.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.