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):
- Strict Req profile fetch (D-20)
- Pre-parse the metadata-root signature envelope (Pitfall 4 — verify
BEFORE parse-deeply) and route through
Signature.verify_metadata_root/4against the operator-pinned trust anchor (D-16 + D-17) Parser.parse/2(the existing hardened parser — only NOW)CorpusGate.check/2post-parse pre-apply (D-21)DriftDetector.diff/2(D-18)Import.build_candidate/1MetadataApply.apply_revision/4withtrigger: :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].
Summary
Functions
@spec refresh( map(), keyword() ) :: {:ok, struct()} | {:error, Relyra.Error.t()}