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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Per-account `session_version` epoch revocation port
//! (RFC_2026-05-04_jwt-full-adoption Phase 5 — sv-port).
//!
//! Account-wide revocation axis: token's `sv` claim must be `>=` the
//! substrate's current value. break-glass and `LogoutAll` bump the
//! current value, invalidating every prior token within the substrate's
//! TTL window. STANDARDS_AUTH_PPOPPO §17.7 + STANDARDS_AUTH_INVALIDATION
//! §2.3.
//!
//! ── Deep module — composition lives behind the port ────────────────────
//!
//! chat-auth's existing two-trait split (`SessionVersionCache` +
//! `SessionVersionFetcher`) — best-effort cache layer + authoritative
//! fetcher — is a *production-validated* decomposition. It moves into
//! the implementation of this port (an adapter that internally composes
//! cache + fetcher with fail-closed fall-through), NOT onto the engine
//! boundary. The engine sees ONE port; impls decide their own internal
//! structure (chat-auth: cache + DB fetcher; pas-external: cache +
//! HTTP-userinfo fetcher; in-memory: HashMap).
//!
//! This is the deep-module win: the engine's contract is "give me the
//! current epoch for this subject"; substrates with different latency /
//! consistency profiles each compose their own internal stack and
//! satisfy the same trait.
//!
//! ── Failure-mode contract (fail-closed) ────────────────────────────────
//!
//! Returns `Ok(i64)` always when a value can be determined — adapters
//! choose the genesis convention (typical: 0 means "no break-glass yet").
//! A wired validator REFUSES a token whose `claims.sv` is absent (R6
//! legacy-admit retired, RFC_202607150428 S6). `Err(Transient)`
//! signals substrate failure; engine maps to
//! `AuthError::SessionVersionLookupUnavailable` and refuses the token.
//!
//! ── Phase 10 split (RFC §6.11) ──────────────────────────────────────────
//!
//! Moves to `access_token::EpochRevocation` in Phase 10 D1. id_token
//! does not carry an `sv` claim — break-glass invalidates bearer
//! credentials, not the assertion-of-authentication that id_token
//! represents.
/// Current per-account `session_version` lookup.
///
/// `sub` is the subject claim (the account's ppnum_id ULID). The engine
/// calls this whenever the token carries an `sv` claim — Human session
/// tokens AND AI-agent client_credentials tokens minted via PAS's
/// `AgentService.IssueToken` (the AI-agent kill-switch). For tokens with
/// no `sv` (Token-Exchange delegated / exchange paths, legacy tokens) the
/// gate short-circuits before reaching this port.
/// Required typed stance on the sv epoch axis (RFC_202607150428 §7 Q3,
/// resolved 2026-07-17).
///
/// Every [`super::VerifyConfig`] carries exactly one of these — the
/// former `epoch: Option<Arc<dyn EpochRevocation>>` slot let four
/// shipped consumers silently skip the axis (`None` was both "not yet
/// wired" and "deliberately unwired", indistinguishable). Silence is
/// now unrepresentable: a verify site that does not enforce the axis
/// must SAY SO, in a variant whose name and payload are greppable and
/// whose boot declaration is visible in logs (STANDARDS_FAILURE_MODE
/// §4 — emission IS declaration).
///
/// ── Variant semantics ──────────────────────────────────────────────
///
/// - [`Enforced`](Self::Enforced) — the engine gates every verify
/// through the port (`check_epoch::run`): missing `sv` rejects
/// (`unstamped`, S6), stale `sv` rejects, substrate-down rejects
/// fail-closed. The only variant that reaches the port.
/// - [`EnforcedElsewhere`](Self::EnforcedElsewhere) — the axis IS
/// enforced on this request path, by a named gate outside this
/// engine call. Sole legitimate case today: PAS self-verify, where
/// `port.current(sub)` would self-loop into the same
/// `session_version_repo` that performs the bump, so the
/// `session::liveness` single gate checks `sv` AFTER verify
/// (`accounts-api/src/auth.rs`). Boot-declares at INFO — a correct
/// configuration must not cry wolf at WARN.
/// - [`Unenforced`](Self::Unenforced) — a declared, bounded gap: this
/// deployment has no epoch substrate (RCW/CTW: no KVRocks, separate
/// DB, published-SDK consumers — RFC_202607150428 §6·S5 deferral).
/// Boot-declares at WARN so the gap is permanently visible in logs.
///
/// The engine short-circuits (admits past the sv axis) for both named
/// non-`Enforced` variants — identical wire behavior to the retired
/// `None`, but the stance is now typed, named, and logged.