# Part 7 — revocation and rotation

## Revoking a device

```ts
const { rotated, failed, owed } = await engine.revokeDevice('sam-phone');
```

The relay stops handing that device key material and flags every graph it held
keys for. This engine then rotates the graphs it can.

**Check all three results.**

| field     | means                                                                     |
| --------- | ------------------------------------------------------------------------- |
| `rotated` | graphs successfully rotated from this device                              |
| `failed`  | graphs whose rotation errored — retry, or rotate from elsewhere           |
| `owed`    | graphs the revoked device held keys for that this device could not rotate |

A non-zero `owed` means **data is still sealed under a key the revoked device
has**. Open those graphs on this device, or rotate from a device that has them.
Saying nothing about it would be the quiet half of a security action that
looked like it worked.

A device cannot revoke itself — do it from another device.

## Rotating by hand

```ts
const epoch = await engine.rotate('g:acme');
```

A rotation mints a fresh epoch wrapped to **every** active device that remains.
The relay refuses one that would strand anybody, naming the missing device IDs,
rather than silently splitting the graph in half.

## What rotation does and does not do

```mermaid
timeline
  title what a revoked device can read
  before revocation : everything it already synced (physics)
  at revocation     : the relay stops serving it — 403 on every graph route
  after rotation    : nothing sealed under the new epoch
```

It keeps what it already synced. That is physics, not policy. Rotation is what
makes it the **last** thing it ever reads.

## Holding writes while behind

A device that has not received the current key **holds its writes** rather than
sealing them under the previous one — which whoever the rotation excluded still
holds. Unsent is recoverable; sent under the wrong key is not.

You will see this as `status.pending > 0` plus an `error` event with scope
`keys.behind`. The fix is for another device to sync (which grants), and it
usually happens on its own within seconds.

Since the relay reports each graph's epoch on every push and pull, a rotation
reaches the other devices on their **next sync** — not the next time they meet
a record they cannot open.

---
