# `Mobus.Stepwise.Artifacts`
[🔗](https://github.com/fosferon/mobus_stepwise/blob/main/lib/mobus/stepwise/artifacts.ex#L1)

Canonical helpers for workflow-owned artifacts in the stepwise runtime.

Artifacts are durable, workflow-scoped data that should survive refreshes,
retries, and external callbacks (e.g. email confirmation links).

# `artifact_entry`

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

# `artifact_map`

```elixir
@type artifact_map() :: %{optional(String.t()) =&gt; artifact_entry()}
```

# `merge`

```elixir
@spec merge(map() | nil, map() | nil) :: artifact_map()
```

Merges two artifact maps, with incoming entries overriding existing ones.

Both maps are normalized before merging. `nil` inputs are treated as empty maps.

## Parameters

  * `existing` — the current artifact map (or `nil`)
  * `incoming` — new artifacts to merge in (or `nil`)

## Returns

  * A merged and normalized `artifact_map()`.

## Examples

    Artifacts.merge(%{"a" => %{"data" => 1}}, %{"b" => %{"data" => 2}})
    #=> %{"a" => %{...}, "b" => %{...}}

# `normalize`

```elixir
@spec normalize(map() | nil) :: artifact_map()
```

Normalizes an artifact map into canonical format.

Each artifact entry is coerced to include `"kind"`, `"version"`, `"inserted_at"`,
and `"data"` keys with string keys throughout. Keys are stringified, and entries
without explicit metadata default to kind `"opaque"` and version `1`.

Returns an empty map for `nil` or non-map inputs.

## Parameters

  * `artifacts` — a map of artifact entries, or `nil`

## Returns

  * A normalized `artifact_map()` with string keys and canonical entry structure.

## Examples

    Artifacts.normalize(%{report: %{kind: "pdf", data: "..."}})
    #=> %{"report" => %{"kind" => "pdf", "version" => 1, "inserted_at" => "...", "data" => "..."}}

    Artifacts.normalize(nil)
    #=> %{}

---

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