Lightning.Collaboration.WorkflowResolver (Lightning v2.16.8-pre)

View Source

Single authority for resolving "given a workflow_id and an action, what %Workflow{} should a collaboration session edit, and in which Ecto state?".

Both the channel join and the session save delegate here so they cannot disagree on whether an id maps to a :built or :loaded struct — the structural root cause behind the workflows_pkey duplicate INSERT on collab reconnect (#4830).

The resolver performs no authorization (Permissions.can stays at the channel). It does enforce project-ownership, but only when a :project opt is supplied, because ownership is a property of the resolved row, not of the user.

Summary

Types

The discriminant the resolver returns alongside the workflow, so callers read newness from the tag rather than re-deriving it from struct shape

Functions

Resolves the %Workflow{} a collaboration session should edit.

Hydrates a read-only %Workflow{} from a specific snapshot version.

Types

action()

@type action() :: :new | :edit

kind()

@type kind() :: :new | :existing | :version

The discriminant the resolver returns alongside the workflow, so callers read newness from the tag rather than re-deriving it from struct shape:

  • :new:new action with no existing DB row (a freshly built :built struct).
  • :existing — a :new action for an id that already has a row, or an :edit action (loaded row).
  • :version — a snapshot version-view (resolve_version/3).

resolve_opts()

@type resolve_opts() :: [
  version: non_neg_integer() | nil,
  project: Lightning.Projects.Project.t() | nil
]

Functions

resolve(workflow_id, action, opts \\ [])

@spec resolve(
  workflow_id :: Ecto.UUID.t(),
  action :: action(),
  opts :: resolve_opts()
) ::
  {:ok, Lightning.Workflows.Workflow.t(), kind()}
  | {:error,
     :workflow_not_found
     | :wrong_project
     | :snapshot_not_found
     | :invalid_action}

Resolves the %Workflow{} a collaboration session should edit.

Returns {:ok, workflow, kind} where workflow is already in the correct Ecto state (with jobs, edges, and triggers populated and the has_auth_method flag set on each trigger) and kind is the newness discriminant (see kind/0).

Cases:

  • action: :new, no existing row — a freshly built %Workflow{} keyed on workflow_id, with project_id from the supplied project, empty jobs/edges/triggers, and lock_version: 0. Ecto state :built, kind :new. The first-INSERT case.
  • action: :new, row already exists — the persisted workflow loaded with jobs/edges/triggers. Ecto state :loaded, kind :existing. Resolving an existing id to its loaded row routes the save to UPDATE rather than a duplicate INSERT (#4830).
  • action: :edit, no version, row exists — the persisted workflow loaded with jobs/edges/triggers and has_auth_method set. Ecto state :loaded, kind :existing.
  • action: :edit, no version, no row — {:error, :workflow_not_found}.
  • unknown action — {:error, :invalid_action}.

resolve_version(workflow_id, version, opts \\ [])

@spec resolve_version(
  workflow_id :: Ecto.UUID.t(),
  version :: non_neg_integer(),
  opts :: resolve_opts()
) ::
  {:ok, Lightning.Workflows.Workflow.t(), kind()}
  | {:error, :snapshot_not_found}

Hydrates a read-only %Workflow{} from a specific snapshot version.

Dispatched from resolve/3 when a non-nil :version opt is present, and may also be called directly. The version is a non_neg_integer() already parsed by the caller — the resolver never parses version strings.

Returns a %Workflow{} in Ecto state :built carrying the snapshot's lock_version and tagged with kind :version. A version-view is a read-only point-in-time view that is never saved through this path; the :version kind lets callers distinguish it from a genuinely-new workflow.

Sets project_id from the supplied project and performs no ownership check, unlike the :edit latest path. Performs no authorization (auth stays at the channel).

Returns {:error, :snapshot_not_found} when no snapshot exists for the version.