Chimeway.Workflows.Progression (chimeway v1.0.0)

Copy Markdown View Source

Durable workflow progression engine for Phase 25.

This is the single seam through which workflow runs move from active to waiting and from waiting/active to the next step. It evaluates the active step's normalized progress rules against the canonical prior delivery row inside one transaction, persisting the decision durably so that:

  • wait_until rules write the run to :waiting with explicit status_reason: "waiting_for_step_progression" and a status_context map carrying rule_kind, anchor, anchor_delivery_id, anchor_delivery_status, anchor_timestamp, due_at, and to_step (D-01/D-13).
  • on_outcome rules append a workflow_transition with reason progressed_on_delivery_outcome and the curated workflow outcome plus raw evidence facts (D-12), then advance the workflow run cursor to the target step and emit the next-step delivery through the canonical Chimeway.DeliveryPlanning.plan_next_step_delivery/3 seam (D-10).

Re-entry is duplicate-safe: if the run is no longer :active, if the prior delivery is not converged yet, if the curated mapper returns :not_branchable_yet, or if no progress rule matches, the engine returns {:noop, run, reason} without creating any new delivery rows or appending transitions. This is the ESC-03 contract.

All locking happens inside one Repo.transaction/1:

  • the workflow run row is locked with FOR UPDATE first
  • the active-step delivery row is then locked with FOR UPDATE

Threats covered:

  • T-25-04 (tampering): outcomes are derived from canonical persisted rows only via ProgressionOutcome.from_delivery/2; the engine never branches from queue or in-flight job state.
  • T-25-05 (repudiation): explicit status_reason and reason strings plus the curated status_context / transition context keys make the decision auditable from durable rows alone.
  • T-25-06 (DoS / duplicate emission): noop short-circuits prevent retry storms from emitting duplicate next-step deliveries.

Summary

Functions

Lists workflow runs that are currently :waiting with a due wait gate that has elapsed (per persisted status_context["due_at"]) and re-evaluates each one through progress_run/2. The Plan 25-03 due-step worker calls into this helper so wait gates always advance through the same shared seam.

Evaluates the workflow run's active step against the canonical prior delivery row and persists the resulting waiting/advanced/noop outcome.

Types

progress_result()

@type progress_result() ::
  {:ok,
   {:advanced, Chimeway.Workflows.WorkflowRun.t(), [Chimeway.Delivery.t()]}}
  | {:ok, {:waiting, Chimeway.Workflows.WorkflowRun.t()}}
  | {:ok, {:completed, Chimeway.Workflows.WorkflowRun.t()}}
  | {:ok, {:stopped, Chimeway.Workflows.WorkflowRun.t()}}
  | {:ok, {:noop, Chimeway.Workflows.WorkflowRun.t() | nil, atom()}}
  | {:error, term()}

Functions

progress_due_runs(opts \\ [])

@spec progress_due_runs(keyword()) :: [progress_result()]

Lists workflow runs that are currently :waiting with a due wait gate that has elapsed (per persisted status_context["due_at"]) and re-evaluates each one through progress_run/2. The Plan 25-03 due-step worker calls into this helper so wait gates always advance through the same shared seam.

progress_run(workflow_run_id, opts \\ [])

@spec progress_run(
  Ecto.UUID.t(),
  keyword()
) :: progress_result()

Evaluates the workflow run's active step against the canonical prior delivery row and persists the resulting waiting/advanced/noop outcome.

Options:

  • :nowDateTime.t() used as the evaluation time for due-checks and anchor stamping. Defaults to DateTime.utc_now/0. Provided for deterministic tests and the due-step worker (Plan 25-03).