# `Relyra.Metadata.AutoRefresh`
[🔗](https://github.com/szTheory/relyra/blob/v1.1.0/lib/relyra/metadata/auto_refresh.ex#L1)

Phase 21 scheduled-refresh wrapper per D-05. Does NOT re-implement
`Relyra.Metadata.Refresh.refresh/2` — wraps it from outside, inserting
the asymmetric-strictness checks D-15..D-21 BEFORE any deep parse.

Linear pipeline (every stage is a refusal point):

  1. Strict Req profile fetch (D-20)
  2. Pre-parse the metadata-root signature envelope (Pitfall 4 — verify
     BEFORE parse-deeply) and route through
     `Signature.verify_metadata_root/4` against the operator-pinned
     trust anchor (D-16 + D-17)
  3. `Parser.parse/2` (the existing hardened parser — only NOW)
  4. `CorpusGate.check/2` post-parse pre-apply (D-21)
  5. `DriftDetector.diff/2` (D-18)
  6. `Import.build_candidate/1`
  7. `MetadataApply.apply_revision/4` with `trigger: :scheduled_refresh`
     (Plan 04 transactional D-28 path)

Any refusal short-circuits to `MetadataApply.record_attempt/3` with
the appropriate typed `auto_suspended_reason` so the LOCKED enum from
Plan 01 is honored. Five refusal classes route to typed reasons:

  | Refusal                       | auto_suspended_reason          |
  |-------------------------------|--------------------------------|
  | TrustAnchor.check/2           | :trust_anchor_mismatch         |
  | Signature.verify_metadata_root| :signature_invalid             |
  | CorpusGate.check/2            | :corpus_violation              |
  | DriftDetector entity_id_drift | :entity_id_drift               |
  | DriftDetector new_signing_cert| :new_signing_cert              |
  | (5 transient default)         | :transient_failures_exceeded   |

D-39: every emit + every record_attempt call carries the batch's
`correlation_id` from `opts[:audit][:correlation_id]`.

# `refresh`

```elixir
@spec refresh(
  map(),
  keyword()
) :: {:ok, struct()} | {:error, Relyra.Error.t()}
```

---

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