# `Runic.Workflow.Runnable`
[🔗](https://github.com/zblanco/runic/blob/main/lib/workflow/runnable.ex#L1)

A prepared unit of work ready for execution.

Contains everything needed to execute independently of the source workflow.
After execute/2, contains result and events for reducing back into workflow.

## Three-Phase Execution Model

1. **Prepare** - Extract minimal context from workflow, build a Runnable
2. **Execute** - Run the node's work function in isolation (potentially parallel)
3. **Apply** - Fold events back into the workflow via `apply_event/2`

The Runnable struct is the carrier between these phases, holding:
- The node to invoke
- The input fact triggering invocation
- Minimal causal context (no full workflow reference)
- After execution: result, status, and events for reducing into workflow

# `status`

```elixir
@type status() :: :pending | :completed | :failed | :skipped
```

# `t`

```elixir
@type t() :: %Runic.Workflow.Runnable{
  context: Runic.Workflow.CausalContext.t() | nil,
  error: term() | nil,
  events: [struct()] | nil,
  hook_apply_fns: [function()] | nil,
  id: integer() | nil,
  input_fact: Runic.Workflow.Fact.t(),
  node: struct(),
  result: term() | nil,
  status: status()
}
```

# `complete`

```elixir
@spec complete(t(), term(), [struct()]) :: t()
```

Marks a runnable as completed with result and events.

Events are the list of event structs produced by `Invokable.execute/2`.
They will be folded into the workflow via `apply_event/2` during the apply phase.

# `complete`

```elixir
@spec complete(t(), term(), [struct()], [function()]) :: t()
```

Marks a runnable as completed with events and hook apply_fns.

# `fail`

```elixir
@spec fail(t(), term()) :: t()
```

Marks a runnable as failed with an error.

# `new`

```elixir
@spec new(struct(), Runic.Workflow.Fact.t(), Runic.Workflow.CausalContext.t()) :: t()
```

Creates a new Runnable in pending state.

The id is a hash of {node.hash, fact.hash} for idempotency tracking.

# `new`

```elixir
@spec new(
  integer(),
  struct(),
  Runic.Workflow.Fact.t(),
  Runic.Workflow.CausalContext.t()
) :: t()
```

Creates a new Runnable with explicit id.

# `runnable_id`

```elixir
@spec runnable_id(
  struct(),
  Runic.Workflow.Fact.t()
) :: integer()
```

Generates a stable runnable id from node and fact hashes.

# `skip`

```elixir
@spec skip(t(), [struct()]) :: t()
```

Marks a runnable as skipped with events.

The events (typically just `ActivationConsumed`) are folded during apply,
and downstream nodes are marked as `:upstream_failed`.

---

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