1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Client-side persistence contract — [`CredentialStore`] + the shared
//! [`StoreError`].
//!
//! `cheers-core` keeps only the *device-side* store trait: [`CredentialStore`],
//! the opaque-blob credential storage a native client (keyring, encrypted-file,
//! in-memory) implements. The *origin-side* store traits — `UserStore` and
//! `RefreshStore` — moved to `cheers-server` (R019-F6), so a verify-only or
//! device-only consumer never even names them. [`StoreError`] stays here: it's
//! the shared error every store and the revocation traits return.
//!
//! All traits are `async` via [`async_trait`] so they remain dyn-compatible.
//!
//! `StoreError` here is the **adapter-facing** error; R007-T4 lands the
//! workspace-wide error hierarchy and re-exports a unified type.
//!
//! @yah:ticket(R019-F4, "Revocation read/write split: RevocationWriter (origin) + RevocationReader (edge)")
//! @yah:assignee(agent:claude)
//! @yah:at(2026-05-26T17:52:56Z)
//! @yah:status(review)
//! @yah:parent(R019)
//! @yah:next("Promote store.rs's 'cheers does not enforce revocation server-side; the product wires up the check' note into two traits: RevocationWriter { revoke(jti | chain) } (origin, Yubaba Redis/gossip) and RevocationReader { is_revoked(jti) } (edge, local replica / CF KV).")
//! @yah:next("Eventually-consistent by documented contract; the short access-token TTL is the stated propagation bound. Wire revoke() into logout + UserStore::revoke_device + RefreshStore::revoke_chain.")
//! @yah:next("Keyed on the token's jti — depends on the Claims.jti field added alongside the facades feature.")
//! @yah:verify("cd external/cheers && cargo test -p cheers-core")
//! @arch:see(.yah/docs/working/edge-verifiable-auth.md)
//! @yah:handoff("Landed RevocationReader{is_revoked(jti)} + RevocationWriter{revoke(jti)} in new revocation.rs, exported from lib.rs. Reader = edge hot path (point membership check), Writer = origin cold path; both async + Send+Sync + dyn-compatible, mirroring the store.rs traits. The read/write split is the same capability-by-type discipline as TokenVerifier/TokenMinter.")
//! @yah:handoff("Settled the 'revoke(jti | chain)' shape: the WRITER is jti-only. Chain/device revocation = RefreshStore::revoke_chain (blocks re-issue on the cold path) composed with per-jti revoke + natural expiry of in-flight access tokens within the access TTL. The module doc owns the full eventually-consistent contract (revoke propagates async; access-token TTL is the staleness bound; sound because auth has no cross-session OLTP). store.rs revoke_device doc promoted to point at the new traits.")
//! @yah:handoff("jti landed on Claims (claims.rs) as F4's revocation key — nominally an F3 line-item, but F4 keys on it so it moved up. #[serde(default, skip_serializing_if=String::is_empty)] keeps the wire/cookie format byte-identical when unset; with_jti() builder; Claims::new() kept at 5 args so existing + cross-camp (mesofact R009) call sites still compile.")
//! @yah:handoff("Verified GREEN: cargo test -p cheers-core (45 unit incl. 4 revocation + jti tests, 9 proptest, 3 doctest) + cargo check --workspace --all-features. NOTE: revocation.rs + store.rs doc-link to crate::session::* (SessionAuthority/EdgeVerifier/SessionPolicy), which land in R019-F3 — forward refs that resolve when F3 lands; cargo test/check don't validate intra-doc links, only cargo doc does.")
//! @yah:handoff("Facade-level wiring (SessionAuthority composing revoke_chain + revoke; EdgeVerifier consulting is_revoked after signature check) is R019-F3 — picked up next per the maintainer's F4-first ordering.")
use async_trait;
use crateCredential;
/// Errors a store impl may return.
/// Opaque-blob credential storage, keyed by a caller-chosen string.
///
/// The one store trait the device tier needs: native-client features (P8)
/// implement it over keyring, encrypted-file, or in-memory backends.
/// `Credential::material` is the provider-specific blob; cheers-core does not
/// interpret it. Kept in `cheers-core` (not `cheers-server`) because the client
/// stores credentials without ever touching a token codec.