Issue #3608405: Harden the interaction dispatcher: a signal must be scoped to its branch token

Closes #3608405 on drupal.org: https://www.drupal.org/project/orchestra/issues/3608405

A signal carries one step's outcome, so it must name its step. This makes signals branch-scoped end to end:

  • InteractionContext::signalUrl() always mints a branch-scoped capability; the instance-scoped signalUrl() minting is removed from InteractionUrlsInterface, InteractionUrls and both gateways.
  • CapabilityResolver::resolveSignal() denies an instance-scoped grant (it may still view and dispatch) and returns NULL for a stale branch token, so a double click or prefetch no-ops; the controller then redirects scoped to the signalled branch.
  • The continuation path follows the same rule: CapabilityContinuationResolver refuses an instance-scoped token as a handle and ResolvedContinuation::tokenId is non-nullable, removing the webform handler's resume-whatever-is-parked fallbacks.
  • Kernel coverage: instance-scoped signal denied with the step parked, stale signal no-op with a branch-scoped redirect, the outcome allow-list and assigned-step guards re-scoped, and an instance-scoped token neither binds nor resumes a webform submission.

Verified: full kernel sweep, the Functional and FunctionalJavascript suites, phpcs and cspell, plus the yoyaku consumer kernel suite.

Merge request reports

Loading