Maybe a naive question: but is there any way at all (impractical or not) to implement stateless revocation? In my mind JWT is already non-stateless in the sense that the server at least needs to keep one piece of information which is the secret key. It's very memory efficient, but not stateless. If a decision is made to revoke a user, that state needs to be kept somewhere.
No. But a stateful revocation service can be extremely lightweight. The number of JWTs being revoked will be minimal and they are only revoked until they would otherwise expire, which keeps the dataset small. This could easily fit in an in memory cache to avoid a database hit.
You could make your expiration short enough that you are unlikely to need revocation, and allow token exchange (trade an expired token for a new one, and that would include a revocation check)