Skip to content

Part 7 — revocation and rotation

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.

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.

before revocationeverything italready synced(physics)at revocationthe relay stopsserving it — 403 onevery graph routeafter rotationnothing sealedunder the newepochwhat a revoked device can read

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

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.