# `Relyra.Metadata.Scheduler`
[🔗](https://github.com/szTheory/relyra/blob/v1.1.0/lib/relyra/metadata/scheduler.ex#L2)

Phase 21 scheduled metadata refresh entry point per D-01.

`run_due/2` is the canonical pure-function entry point any host
scheduler can drive (Oban Cron, Quantum, k8s `CronJob`, fly.io
scheduled machines, plain `mix relyra.refresh_due`). The function:

  1. Generates one `correlation_id` for the batch (D-39).
  2. Queries the partial index (`relyra_metadata_sources_due_idx`)
     for sources whose `next_refresh_at` is in the past AND
     `auto_refresh_enabled = true` AND not currently suspended (D-12).
     The runtime query keeps the suspension/time filter at query
     time (Plan 01 deviation — Postgres rejects STABLE functions in
     partial-index predicates).
  3. If no due rows: emits
     `[:relyra, :saml, :metadata, :auto_refresh, :skipped]` per D-07
     and returns `{:ok, %{}}`.
  4. Otherwise: loops sequentially per Phase 20 BulkActions pattern
     (D-38), invoking `Relyra.Metadata.AutoRefresh.refresh/2` per
     source. The same `correlation_id` flows through every
     per-source `MetadataRevision` + `AuditWriter.append_event`
     co-commit.

D-04: there is NO supervised auto-starting ticker. This module is
dormant until something invokes `run_due/2`.

# `run_due`

```elixir
@spec run_due(
  module(),
  keyword()
) :: {:ok, %{optional(binary()) =&gt; term()}} | {:error, Relyra.Error.t()}
```

Runs scheduled metadata refresh for every due source.

`opts`:
  - `:audit` — map; if `:correlation_id` is missing, one is generated.
  - `:source_ids` — optional list of source IDs to scope this tick
    to a specific subset (used by the LiveView "Resume now" probe in
    Plan 06; bypasses the due-rows query).
  - any opts forwarded to `AutoRefresh.refresh/2` (`:req`, etc.).

Returns `{:ok, %{source_id => result}}` where each result is
`{:ok, %MetadataRevision{}}` or `{:error, %Relyra.Error{}}`.

---

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