Part 7 — revocation and rotation
Revoking a device
Section titled “Revoking a device”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
Section titled “Rotating by hand”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
Section titled “What rotation does and does not do”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
Section titled “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.