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
//! 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"
//! and `claims.sv = None` legacy-admits anyway). `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 (ppnum_id ULID for human paths). For
/// non-human subjects (AI agent, client_credentials), the engine never
/// calls this — `claims.sv` is `None` on those paths and the gate
/// short-circuits.