Rotating a key without downtime is always a five-step dance: issue the new key, ship code that accepts both, cut writers to the new key, drain the last verifiers of the old key, then revoke. Skip any step and something breaks — usually the least-observed clients. Set the acceptance overlap to the longest possible in-flight signature, add cache-clock skew, and this generates the full runbook with wall-clock times, owners, and the two paste-ready snippets (dual-verify middleware + revoke script) you actually need.
Ship this before touching any signing code. It accepts either key so the swap can be a single env-var change with no coordinated deploy.
exp=+1h issued one second before you revoke the old key will fail verification 59 minutes later. The verify soak has to be at least max(TTL, longest_replay_window), plus clock skew.kid or key label, caches can't tell them apart during overlap. Always issue a fresh identifier.key_id=old makes that visible.