# Retention and purge (/concepts/retention-and-purge)



Vague retention promises are easy to make and impossible to verify. Zumik splits deletion into two operations with sharply different meanings, returns signed evidence for the stronger one, and never claims a guarantee its underlying infrastructure cannot actually deliver.

## Delete is not purge [#delete-is-not-purge]

| Operation                         | Meaning                                               |
| --------------------------------- | ----------------------------------------------------- |
| `DELETE /v2/artifacts/{id}`       | Revoke the customer's access to a handle immediately  |
| `POST /v2/purge-jobs`             | Remove retained representations and generate evidence |
| `GET /v2/purge-jobs/{id}/receipt` | Retrieve the signed purge receipt                     |

A delete is instant and cheap: the handle stops working. A purge is a workflow that reaches into every place a representation might persist and produces a receipt you can audit.

## What a purge job does [#what-a-purge-job-does]

<Steps>
  <Step title="Revoke and tombstone">
    Public handles stop working immediately; artifacts and bundles are marked tombstoned; dependent sessions and snapshots are invalidated.
  </Step>

  <Step title="Bump the namespace generation">
    The isolation-namespace purge generation increments. This is the resurrection-prevention step (see below).
  </Step>

  <Step title="Remove canonical state">
    Object-store blobs and materializations are removed; retained traces are removed according to their [trace mode](/guides/replay); runtime cache namespaces are invalidated.
  </Step>

  <Step title="Request provider-side deletion">
    Where the provider supports it (read from its [capability manifest](/concepts/capability-manifests)), provider-side deletion is requested and acknowledgments recorded.
  </Step>

  <Step title="Issue a signed receipt">
    A signed receipt records each processor's status, the overall guarantee class, and any remaining backup or provider-expiry window.
  </Step>
</Steps>

## The receipt [#the-receipt]

```json
{
  "id": "pur_01jy...",
  "object": "purge_receipt",
  "requested_at": "2026-06-09T20:00:00Z",
  "completed_at": "2026-06-09T20:04:10Z",
  "scope": { "project_id": "prj_...", "artifact_ids": ["art_..."] },
  "guarantee": "verified_namespace_invalidation",
  "processors": [
    { "name": "state_store",  "status": "purged" },
    { "name": "object_store", "status": "purged" },
    { "name": "byoc_runtime", "status": "namespace_invalidated" },
    { "name": "backup_store", "status": "expires_by",
      "expires_at": "2026-07-09T20:04:10Z" }
  ],
  "receipt_digest": "sig_..."
}
```

## Guarantee classes [#guarantee-classes]

The `guarantee` field states exactly how strong the deletion is, from weakest to strongest:

| Class                             | Meaning                                                   |
| --------------------------------- | --------------------------------------------------------- |
| `access_revoked`                  | Customer handles stop working immediately                 |
| `best_effort_expiry`              | A provider-controlled cache expires under provider policy |
| `verified_namespace_invalidation` | Stale physical cache can no longer be reused              |
| `verified_physical_purge`         | Managed storage and cache processors confirmed deletion   |
| `cryptographic_purge`             | Key destruction makes encrypted retained bytes unreadable |

<Warning>
  The receipt's overall guarantee is the **weakest** across all processors. One expiry-bound backend caps the whole receipt at `best_effort_expiry`, even if every other processor confirmed a physical purge. The platform never reports the strongest path and hides a weaker one.
</Warning>

This is why guarantees are honest. A provider whose [manifest](/concepts/capability-manifests) says `manual_cache_clear_supported: false` cannot be physically purged on demand, so a purge touching it yields at most `best_effort_expiry` with the expiry window stated - not a false claim of immediate deletion.

## Resurrection prevention [#resurrection-prevention]

The hardest part of deletion is making sure deleted content cannot quietly come back. After a purge:

* old public IDs stay invalid forever;
* the namespace generation has incremented, so every stale KV entry now &#x2A;*fails the [KV-compatibility check](/concepts/identity-model)** and cannot be served;
* re-uploading identical content creates a brand-new opaque handle - it does not restore the old one;
* internal [fingerprint](/concepts/handles-and-fingerprints) matches do not automatically rebuild deleted relationships.

<Note>
  The namespace generation lives inside the KV-compatibility key. Bumping it by one is enough to orphan every prior physical cache entry in that namespace at once, which is why purge can guarantee that stale state will not be reused even before every byte is physically gone.
</Note>

<CardGroup cols="2">
  <Card title="Purge semantics guide" icon="trash-can" href="/guides/purge-semantics">
    Run a purge and read the receipt.
  </Card>

  <Card title="Data privacy and retention" icon="shield" href="/security/data-privacy-and-retention">
    Default retention posture and trace modes.
  </Card>
</CardGroup>
