Daevix Docs

Offboard with crypto-shred

Erase an org's data irreversibly over the operator API: open a grace window, then destroy the org's encryption key so every value encrypted under it becomes permanently unreadable, and sweep the dead rows.

View as Markdown Edit on Tangled

This guide covers org offboarding: the two-step erasure lifecycle, what each step destroys, and what it leaves alone. Read Data residency first for why the keys are per-org in the first place.

What crypto-shredding destroys

Every controlplane value that an org owns is encrypted at rest under that org’s own key, named org-<id>-secrets in the operator KMS (internal/kms/crypter.go, OrgKeyName). The key encrypts only key-wrapping material: each stored value carries its own data encryption key, and the org key wraps that. Destroy the org key and the wrapped keys can no longer be unwrapped. Every value under the key becomes permanently unreadable, and database access cannot recover it.

CompleteErasure destroys the key first and sweeps the dead rows second (internal/controlplane/offboarding/offboarding.go). If the process crashes in between, the leftover rows are unreadable ciphertext. They are not readable orphans.

The sweep deletes four row families from the controlplane database:

  • policy settings for the org
  • policy agent overrides for the org
  • config values for the org
  • config agent overrides for the org

After the sweep the org’s status becomes erased. The shred is complete the instant the key is gone; the rows are only tombstones that the sweep drops.

The two steps

Offboarding is a two-step lifecycle, so an accidental trigger can be caught before anything is destroyed:

active --begin--> destroying --complete--> erased

The first step opens the grace window. It marks the org destroying and destroys nothing:

offboard, step 1
 curl -X POST -H "Authorization: Bearer $TOKEN" \    https://<controlplane>/api/v1/orgs/<org>/erasure{"org_id":"<org>","status":"destroying"}

A 202 response confirms the request. The status code is not 201 or 200 because the destructive half has not run. There is no timer on the grace window; it stays open until you complete it. There is also no documented cancel route that closes the window and returns the org to active: no erasure endpoint undoes the begin step. The only path that sets an org back to active is a side effect of the org upsert: POST /api/v1/orgs with the same id and an empty status writes the org row with status active (internal/controlplane/iam/api.go, createOrg; internal/controlplane/iam/store.go, UpsertOrg). That route exists for creating orgs, not for canceling erasure, so treat it as an undocumented recovery path and not a supported cancel.

The second step is the irreversible one. It destroys the org key, sweeps the rows, and marks the org erased:

offboard, step 2
 curl -X POST -H "Authorization: Bearer $TOKEN" \    https://<controlplane>/api/v1/orgs/<org>/erasure/complete{  "org_id": "<org>",  "status": "erased",  "key_shredded": true,  "rows_swept": 12}

Complete the erasure only from the grace window. Calling it on an org in any other state is a 409 conflict, so a single call can never destroy an org.

The key_shredded value reports the outcome accurately. It is false when no operator KMS is configured, or when the KMS provider cannot delete keys (the provider reports ErrUnsupportedOperation). In those cases the rows are still swept, but the ciphertext left behind was not key-shredded, and the response reports that with key_shredded: false. A KMS error during the destroy aborts the erasure before any row is swept.

Who can run it, and what is refused

Both routes require the platform:orgs capability at platform scope over the bearer-authenticated operator API. See Operator API for the authentication model. An agent-operator caller is refused at the actor-kind check before grants are even loaded. The platform org itself cannot be erased: the shred would destroy the operator’s own key material, so the route returns 403 before touching anything. An unknown org is 404.

Every step is audited at authority tier. The controlplane.org.erasure.begin event is durably captured before the status commit, so a destruction is never unrecorded. controlplane.org.erasure.complete reports key_shredded, rows_swept, and the final status. See Audit events for the field lists.

What the erasure does not destroy

Erasure is narrower than it looks. Know these four boundaries before you run it.

The org row survives. The status becomes erased; the org still exists. Deleting the org row is a separate, idempotent step:

remove the erased org
 curl -X DELETE -H "Authorization: Bearer $TOKEN" \    https://<controlplane>/api/v1/orgs/<org>

The platform org is undeletable as well as unerasable. A DELETE on an org that does not exist is a 204 either way.

Users, groups, grants, and service accounts are not swept. The erasure sweep covers policy and config rows only. Those identity rows keep existing until you delete the org or remove them by their own routes.

The enclave is never dialed. The controlplane holds no credential for the enclave’s KMS and performs no operation against it. The org’s sovereign secrets and sealed inference bodies live in the enclave’s own database under the enclave KMS’s per-org key. The enclave-side store implements the same crypto-shred (internal/enclave/sovereign/store.go, CryptoShredOrg), but no API exposes it yet. Today, destroying an enclave’s recoverable data means tearing down the enclave and destroying its KMS keys, as described in Data residency.

A deployment with no operator KMS has no shred to report. Policy and config values are then stored in plaintext. Erasure still sweeps the rows, and it still destroys nothing else, but key_shredded reports false because no key existed to destroy.