ppoppo-sdk-core 0.2.0

Internal shared primitives for the Ppoppo SDK family (pas-external, pas-plims, pcs-external) — verifier port, audit trait, session liveness port, OIDC discovery, perimeter Bearer-auth Layer kit, identity types. Not a stable public API; do not depend on this crate directly. Consume the SDK crates that re-export from it (e.g. `pas-external`).
Documentation
//! Ppoppo SDK shared primitives — internal mechanism for the
//! Ppoppo SDK family (`pas-external`, future `pas-plims`, `pcs-external`).
//!
//! ## Stability — not a stable public API
//!
//! This crate is published to crates.io as a transitive dependency of
//! the SDK family above (cargo enforces "all transitive deps of a
//! published crate must themselves be published"). It is **not** a
//! stable public API; it carries no SemVer guarantees beyond the
//! current minor and may rearrange shapes between minors. Consume
//! the SDK crates that re-export from it (`pas-external::*`,
//! eventually `pas-plims::*` / `pcs-external::*`); do not depend on
//! `ppoppo-sdk-core` directly from a 3rd-party application.
//!
//! Phase A foundation (RFC `RFC_2026-05-08_app-credential-collapse.md`)
//! — collapses the verifier port + audit trait + session-liveness port +
//! discovery primitive + perimeter Bearer-auth Layer kit + identity
//! types out of `pas-external` so multiple SDK crates and 1st-party
//! services (chat-auth, chat-api) all consume the same primitives.
//!
//! ## Surface (Slice 1a)
//!
//! - `error::SdkCoreError` — narrow error type for sdk-core primitives.
//! - `types::{Ppnum, PpnumId, SessionId, UserId, KeyId}` — SDK-shared
//!   identity types. `Ppnum::TryFrom<String>` is the validated boundary.
//! - `audit::*` — `AuditSink` trait + `AuditEvent` + `VerifyErrorKind` +
//!   `IdTokenFailureKind` + `RateLimiter` trait + `RateLimitKey` +
//!   `compose_source_id` + `compose_id_token_source_id`. Plus the
//!   in-tree utility impls (`NoopAuditSink`, `MemoryAuditSink`,
//!   `MemoryRateLimiter`, `RateLimitedAuditSink`) — utility impls live
//!   here too because the audit module is one cohesive unit.
//! - `session_liveness::{SessionLiveness, SessionLivenessError}` —
//!   per-request L2 row liveness port. Wired into the verifier slot.
//!   Trait-only (no `cipher`/`liveness` AES wrapper code — that stays
//!   in pas-external as a feature-gated submodule).
//!
//! ## Surface (Slice 1b — populated when verifier cohesive group lands)
//!
//! - `verifier::*` — `BearerVerifier` trait + `JwtVerifier` impl +
//!   `MemoryBearerVerifier` + `VerifiedClaims` + `VerifyConfig` +
//!   `VerifyError` + `JwksCache`. Audit decision E (cohesive group —
//!   no trait/impl split across crates).
//!
//! ## Surface (Slice 2 — populated)
//!
//! - `discovery::{Discovery, DiscoveryError, fetch_discovery}` — OIDC
//!   discovery document fetch (`<issuer>/.well-known/openid-configuration`).
//!   RFC 8414 §3.3 issuer-mismatch defense lives here. Gated on
//!   `well-known-fetch` (HTTP fetch + URL types).
//!
//! ## Surface (Slice 4 — populated)
//!
//! - `bearer::*` — perimeter Bearer-auth Layer kit. `AuthProvider<S>`
//!   port + `BearerAuthLayer<Sess, P>` tower::Layer + `BearerAuthConfig`
//!   + `VerifyError` + `MemoryAuthProvider<S>` (test-support). Gated on
//!   `axum` feature. 1st-party services (chat-auth) import direct from
//!   `ppoppo_sdk_core::bearer::*` (audit decision B); 3rd-party
//!   consumers (RCW/CTW) reach the same kit through
//!   `pas_external::bearer::*` re-export (audit decision D).
//!
//! ## Surface (Phase E — populated)
//!
//! - `token_cache::{TokenCache, TokenCacheConfig, TokenSource, TokenCacheError,
//!   ClientCredentialsSource}` — JWT retention layer with single-flight
//!   refresh. `TokenCache` owns a `Box<dyn TokenSource>`; the built-in
//!   `ClientCredentialsSource` covers the OAuth2 `client_credentials` grant.
//!   Gated on `token-cache` (cache+trait) and `client-credentials` (HTTP source).
//! - `interceptor::{AuthInterceptor, BearerCredential}` — tonic sync
//!   `Interceptor` that attaches `Authorization: Bearer <token>` per outgoing
//!   gRPC call. Constructed from a pre-fetched `BearerCredential` (one instance
//!   per call). Gated on `tonic-interceptor`.
//!
//! ## Out of scope (later phases)
//!
//! - `scopes::*` — sealed-trait family parent for SDK-specific scope marker types.

#![deny(rust_2018_idioms)]

pub mod audit;
#[cfg(feature = "axum")]
pub mod bearer;
#[cfg(feature = "well-known-fetch")]
pub mod discovery;
pub mod error;
#[cfg(feature = "tonic-interceptor")]
pub mod interceptor;
pub mod session_liveness;
#[cfg(feature = "token-cache")]
pub mod token_cache;
pub mod types;
pub mod verifier;

// Top-level re-exports — keep narrow (≤ 25 public names budget for Phase A).
pub use error::SdkCoreError;
pub use types::{KeyId, Ppnum, PpnumId, SessionId, UserId};
pub use session_liveness::{SessionLiveness, SessionLivenessError};
pub use audit::{
    AuditEvent, AuditSink, IdTokenFailureKind, MemoryRateLimiter, NoopAuditSink, RateLimitKey,
    RateLimitedAuditSink, RateLimiter, VerifyErrorKind, compose_id_token_source_id,
    compose_source_id,
};
#[cfg(any(test, feature = "test-support"))]
pub use audit::MemoryAuditSink;

// Verifier cohesive group (audit decision E + G).
pub use verifier::{BearerVerifier, VerifiedClaims, VerifyConfig, VerifyError};
#[cfg(feature = "well-known-fetch")]
pub use verifier::{JwksCache, JwtVerifier};
#[cfg(any(test, feature = "test-support"))]
pub use verifier::MemoryBearerVerifier;

// Discovery primitive (Slice 2).
#[cfg(feature = "well-known-fetch")]
pub use discovery::{Discovery, DiscoveryError, fetch_discovery};

// NOTE: bearer kit (`AuthProvider`, `BearerAuthLayer`, `BearerAuthConfig`,
// `VerifyError`, `MemoryAuthProvider`) is intentionally NOT flat-re-exported
// at the crate root. The bearer-side `VerifyError` would collide with the
// verifier-side `VerifyError`; rather than rename one, consumers import
// through the explicit `ppoppo_sdk_core::bearer::*` module path so the
// "this is the perimeter Layer kit, not the token verifier" semantic is
// visible at the import site (audit decision F — flat module under bearer
// itself; keep parent namespaces clean).