Expand description
The write side of the revocation split, and the issuer’s signed revocation set (R732-F6).
Edge-verifiable auth (R019) keeps revocation’s two sides physically apart. This crate holds the origin half:
RevocationWriter— the cold path, run at the origin on logout / device-revoke / membership removal. Records aRevokedentry and advances the store’s epoch.RevocationPublisher— signs the store’s current contents as aRevocationSetunder the issuer key, membership entries tagged under their resource’s revocation key (OwnershipStore::revocation_key; R732-T10), for the publish route and for offline peers to replicate (cheers_verify::ReplicatedRevocations).- The hot-path reader (
cheers_verify::RevocationReader) lives incheers-verify— the verify-only edge holds it without the power to revoke anyone else’s sessions.
The set is eventually consistent by design: a revoke here propagates to
edge readers asynchronously (KV replication / gossip). For access tokens the
access TTL bounds the lag (see SessionPolicy);
for standing credentials (membership snapshots, node bindings) revocation is
the only bound. Full contract on cheers_verify::RevocationReader.
§The epoch
A store is one issuer’s revocation log: it holds a single persistent
epoch, and every write that changes the contents advances it by
next_epoch: max(epoch + 1, now). Strictly monotonic either way; the
clock floor is there so a store restored from an older backup still issues
epochs above the ones replicas already hold — a plain counter would restart
below them, and every replica would refuse every set until it caught up.
Nothing orders sets by wall-clock time: replicas compare epochs only.
§Revoking a session vs. a whole login
§Identity and bound (R732-T7)
A store holds one row per identity ((kind, subject, resource_kind, resource_id), see revoked_columns) carrying one bound — the jti’s
exp, the device’s at_seq, the membership’s at_epoch (see
Revoked). Re-revoking an identity keeps the larger bound, and the epoch
advances only when a row was inserted or its bound rose. A jti with an
exp lapses once exp <= now: store gc drops it, and
RevocationPublisher::current omits it even before gc runs.
§Revoking a session vs. a whole login
RevocationWriter::revoke with Revoked::Jti kills one access token
immediately (edge-visible within the propagation window). The coarser flows
compose it with the refresh cold path:
- Logout / single device-revoke: call
RefreshStore::revoke_chainso the chain can mint no further access tokens, and revoke the session’s currentjti.SessionAuthority::revoke_devicealso recordsRevoked::Device, which is what ends a standing node binding for that device. - Account-wide revoke: revoke every chain via the
UserStoredevice list; same TTL bound applies.
SessionAuthority is where these calls
are composed — it holds both the RevocationWriter and the
RefreshStore.
Structs§
- Memory
Revocation Store - In-process revocation log — tests, and single-process deployments that do not need the set to survive a restart. Clones share one log, so an authority-side writer and an edge-side reader see the same set.
- Revocation
Publisher - Signs a store’s current contents as its issuer’s
RevocationSet. - Revocation
Snapshot - A store’s contents at one epoch, read atomically.
- Revoked
Columns - One entry as a relational row, shared by the SQL stores (cheers-sqlx,
cheers-turso). The key is
(kind, subject, resource_kind, resource_id)— the entry’s identity:subjectis the jti, the device id, or the member’s principal in wire form (user:alice,key:.., migration 0015); the resource columns are""unless the entry is a membership. The bound lives inbound(deviceat_seq, membershipat_epoch;NULLfor a jti) orexpires_at(a jti’sexp;NULL= never lapses). - Signed
Revocation Set - A
RevocationSetsigned under the issuer key.
Traits§
- Revocation
Writer - Origin-side revocation log. Held by
SessionAuthorityon the cold path and byRevocationPublisher.
Functions§
- epoch_
from_ sql - An epoch read back from a signed 64-bit SQL column.
- next_
epoch - The epoch a write advances to:
max(current + 1, now). See the module docs. - revoked_
columns - The row of
entry. Au64bound pasti64::MAXis stored asi64::MAX, which still masks every sequence a store can hand out. - revoked_
from_ columns - The inverse of
revoked_columns.