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
//! Profile-shared verification policy — the JOSE-level fields neither
//! `access_token::VerifyConfig` nor `id_token::VerifyConfig` "owns".
//!
//! ── Why a third struct ──────────────────────────────────────────────────
//!
//! Phase 10.0 split the engine into `access_token::*` + `id_token::*` but
//! left `engine::check_algorithm` / `check_header` / `raw` reading their
//! cfg from `access_token::VerifyConfig`. That conceptually inverts the
//! dependency: the JOSE wire layer doesn't depend on RFC 9068; RFC 9068
//! depends on JOSE. Phase 10.1 (this file) restores the layering — both
//! profile configs *embed* a `SharedVerifyConfig`, and the engine
//! submodules read only from this struct.
//!
//! Adding a field here is a deliberate decision: it must be JOSE-shared
//! (applies to both profiles symmetrically). RFC 9068 access ports
//! (replay/session/epoch revocation) stay on `access_token::VerifyConfig`;
//! OIDC-specific axes (`expected_nonce`, `max_age`, `acr_values`) stay
//! on `id_token::VerifyConfig`.

use crate::algorithm::Algorithm;

/// JOSE-level verification policy shared by both profiles.
#[derive(Debug, Clone)]
pub struct SharedVerifyConfig {
    pub(crate) issuer: String,
    pub(crate) audience: String,
    /// `at+jwt` for RFC 9068 access tokens, `JWT` for OIDC id_tokens.
    /// Pinned at construction by the profile's constructor; the engine
    /// only ever compares against this value (never the wire).
    pub(crate) expected_typ: &'static str,
    pub(crate) max_token_size: usize,
    pub(crate) algorithms: Vec<Algorithm>,
}

impl SharedVerifyConfig {
    /// Construction is `pub(crate)`: only the profile constructors
    /// (`access_token::VerifyConfig::access_token` and
    /// `id_token::VerifyConfig::id_token`) mint instances. Forces every
    /// `SharedVerifyConfig` in the wild to carry a profile-vetted typ
    /// pin, preventing a caller from constructing one with `expected_typ
    /// = ""` and slipping past M13.
    pub(crate) fn new(
        issuer: impl Into<String>,
        audience: impl Into<String>,
        expected_typ: &'static str,
        max_token_size: usize,
        algorithms: Vec<Algorithm>,
    ) -> Self {
        Self {
            issuer: issuer.into(),
            audience: audience.into(),
            expected_typ,
            max_token_size,
            algorithms,
        }
    }
}