# `Chimeway.Workflows.Progression`
[🔗](https://github.com/jonlunsford/chimeway/blob/v1.0.0/lib/chimeway/workflows/progression.ex#L1)

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.

# `progress_result`

```elixir
@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()}
```

# `progress_due_runs`

```elixir
@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`

```elixir
@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:

  * `:now` — `DateTime.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).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
