ppoppo-token 0.3.0

JWT (RFC 9068, EdDSA) issuance + verification engine for the Ppoppo ecosystem. Single deep module with a small interface (issue, verify) hiding RFC 8725 mitigations M01-M45, JWKS handling, and substrate ports (epoch, session, replay).
Documentation
//! 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.
#[async_trait::async_trait]
pub trait EpochRevocation: std::fmt::Debug + Send + Sync {
    async fn current(&self, sub: &str) -> Result<i64, EpochRevocationError>;
}

#[derive(Debug, thiserror::Error)]
pub enum EpochRevocationError {
    /// Substrate composition failure (cache miss + fetcher error,
    /// HTTP userinfo timeout, etc.). Engine maps to
    /// `AuthError::SessionVersionLookupUnavailable`.
    #[error("session_version lookup transient failure: {0}")]
    Transient(String),
}