# `Lightning.Workflows.Spec`
[🔗](https://github.com/OpenFn/lightning/blob/main/lib/lightning/workflows/spec.ex#L1)

Lightning's hand-writable workflow format, and its translation into a
provisioning document.

This is the same format the collaborative editor imports and exports
(`assets/js/yaml/util.ts`) and that workflow templates are written in:
`jobs`, `triggers` and `edges` are **maps keyed by slug**, and they
reference each other by those keys rather than by id.

    name: My Workflow
    jobs:
      transform-data:
        name: Transform data
        adaptor: "@openfn/language-common@latest"
        body: fn(state => state);
    triggers:
      webhook:
        type: webhook
        enabled: true
    edges:
      webhook->transform-data:
        source_trigger: webhook
        target_job: transform-data
        condition_type: always
        enabled: true

`to_document/2` resolves those key references into the id-based document
`Lightning.Projects.Provisioner` consumes (`jobs`, `triggers` and `edges`
as lists, wired together with `source_job_id`, `source_trigger_id`,
`target_job_id`, ...).

It is the Elixir counterpart of `convertWorkflowSpecToState` in
`assets/js/yaml/util.ts` and validates against the very same JSON Schema
(`assets/js/yaml/schema/workflow-spec.json`, read at compile time), so a
workflow that the editor accepts is a workflow this accepts, and vice
versa.

## Options

  * `:id_fun` - `(kind, key -> uuid)` used to mint ids for records that
    don't carry an explicit `id`, where `kind` is `:workflow`, `:job`,
    `:trigger` or `:edge` and `key` is the record's key in the spec (the
    workflow's `name`, for `:workflow`). Defaults to random UUIDs, like the
    editor does; callers that need idempotent re-runs (see
    `Lightning.Kickstart`) pass a deterministic function.

  * `:credentials` - `%{credential_name => project_credential_id}`, used to
    resolve a job's `credential` key into `project_credential_id`. Jobs
    referencing a credential that isn't in the map are an error.

## Not covered

`pos` is accepted (it's part of the format, and the editor round-trips it)
but not carried into the document: the provisioner has no way to set node
positions.

# `id_fun`

```elixir
@type id_fun() :: (kind(), String.t() | nil -&gt; Ecto.UUID.t())
```

# `kind`

```elixir
@type kind() :: :workflow | :job | :trigger | :edge
```

# `spec`

```elixir
@type spec() :: %{required(String.t()) =&gt; any()}
```

# `to_document`

```elixir
@spec to_document(spec(), keyword()) :: {:ok, map()} | {:error, String.t()}
```

Convert a workflow spec into a provisioning document.

See the module docs for the available options.

# `to_document!`

```elixir
@spec to_document!(spec(), keyword()) :: map()
```

Same as `to_document/2`, raising on an invalid spec.

# `validate`

```elixir
@spec validate(spec()) :: :ok | {:error, String.t()}
```

Validate a spec against the workflow-spec JSON Schema (plus the duplicate
job name check the editor also applies).

---

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