# `MishkaCmsCore.Runtime.DraftArchivalPurge`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/draft_archival_purge.ex#L5)

Deletes runtime drafts that were discarded while these resources still soft-deleted.

## What went wrong

`MishkaCmsCore.Runtime.PageDraft` and its two siblings used `AshArchival` until 2026-08-01.
Discarding a draft did not remove the row — it set `archived_at`, and a resource-level
`base_filter_sql "(archived_at IS NULL)"` hid it from every read.

When the extension was removed, discard became a real delete. But the base filter went with it,
so every row still carrying an `archived_at` became **visible again**: a draft its author threw
away is back in the picker, indistinguishable from work in progress. That happened the moment the
code was pulled, before any migration ran.

Dropping the column afterwards does not resurrect anything further — the rows were already
visible — but it does remove the only evidence of which ones were discarded. This module is how
that evidence gets acted on while it still exists.

## What it deletes

Rows matching `archived_at IS NOT NULL AND applied_at IS NULL`, in `page_drafts`, `layout_drafts`
and `error_page_drafts`.

The `applied_at IS NULL` half is not redundant. Nothing in the UI could archive a published draft
— every picker lists pending drafts only — but `Drafts.discard/3` takes an id from the client and
does not check it against that list, so the combination is reachable in principle. A published
draft has already written its content to the live `Page`, and its publish is recorded there;
requiring `applied_at IS NULL` means this purge provably cannot destroy the record of a publish,
rather than merely being unlikely to.

Nothing else needs the same treatment. The old `archive do` blocks declared no `archive_related`,
so the removal orphaned nothing, and the resources that still archive — Page, Layout, Component,
Media, Site, and `MishkaDocument`'s own `*_drafts`, which are a different thing entirely — are
untouched and must stay that way.

## Running it

It is safe on a database that never archived, safe once the column is gone, and safe to run twice
— each of those is a clean no-op reporting zero. It is only USEFUL before the migration that drops
`archived_at`, because afterwards a discarded draft is byte-identical to live work and no query
can tell them apart.

    mix mishka_cms_core.purge_draft_archival --dry-run
    mix mishka_cms_core.purge_draft_archival

In a release there is no Mix, and `bin/migrate` drops the column unattended, so run this first:

    bin/mishka_cms eval 'MishkaCmsCore.Runtime.DraftArchivalPurge.run()'

See `Mix.Tasks.MishkaCmsCore.PurgeDraftArchival` and `MishkaCmsCore.Runtime.PageDraft`.

# `report`

```elixir
@type report() :: %{
  required(String.t()) =&gt; %{
    optional(:deleted) =&gt; non_neg_integer(),
    optional(:found) =&gt; non_neg_integer(),
    optional(:skipped) =&gt; :no_table | :no_column,
    optional(:failed) =&gt; [String.t()]
  }
}
```

# `pending?`

```elixir
@spec pending?() :: boolean()
```

Whether any table still carries the column — what makes this task worth running at all.

# `run`

```elixir
@spec run(keyword()) :: {:ok, report()}
```

Purges the legacy rows and returns what it did, per table.

Options:

  * `:dry_run` — find and count, delete nothing. Default `false`.

---

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