Skip to main content

Module audit

Module audit 

Source
Expand description

M48 + M49 — verify-failure audit emission port + per-source rate limiter (RFC_2026-05-04_jwt-full-adoption Phase 9).

── Why pas-external owns the port, not the schema (β1) ─────────────────

AuditSink is purely abstract. pas-external ships only:

  • NoopAuditSink — explicit “I don’t want audit emission” choice
  • MemoryAuditSinktest-support-gated adapter for boundary verification (downstream consumers’ integration tests)

Production adapters (chat-auth in PCS, future RCW/CTW middleware) live in their own crates and decide their own persistence schema (Postgres table, tracing-subscriber piping into Cloud Logging, etc). The SDK does not bake in sqlx or any specific schema — schema decisions belong to whichever service operates the audit pipeline. See super::token::port::BearerVerifier for the matching port-and-adapter precedent (D-04 γ, locked 2026-05-05).

── Why composition, not orchestration (refinement #1) ─────────────────

[super::token::PasJwtVerifier] holds ONE port (Arc<dyn AuditSink>), not two. Rate-limiting is a property of the sink, expressed by wrapping any sink in a future RateLimitedAuditSink<S, L> (Phase 9.C). This matches epoch_revocation’s deep-module note: composition lives in the adapter layer; the engine sees a single port. Future stacking is free (BatchedAuditSink, AsyncSpawnAuditSink, etc).

── Failure-mode contract — non-blocking ────────────────────────────────

AuditSink::record_failure returns () (no Result). M48 is observability, NOT auth-flow critical. Adapters log internal substrate failures via tracing::error! and continue. The verify hot path MUST NEVER degrade because audit persistence failed; this contract is enforced at the trait surface (no error to bubble in the first place). Callers needing a Result for instrumentation can wrap in a private struct that records the result internally.

── SLA contract ────────────────────────────────────────────────────────

Implementations SHOULD return within 10ms. Heavier work (HTTP roundtrip, batch flush, retry) MUST be spawned onto a background task so the verify hot path is not blocked. The &self (not &mut self) + Send + Sync bounds let one verifier emit concurrently without per-call locking.

── Phase 10 inheritance ────────────────────────────────────────────────

Phase 10.11 (RP middleware — pas-external::oidc::IdTokenVerifier) emits through the same AuditSink port. id_token verify failures and access_token verify failures share the audit pipeline; the VerifyErrorKind enum gains id_token-specific variants in 10.11 without breaking the contract.

Re-exports§

pub use rate_limit::MemoryRateLimiter;
pub use rate_limit::RateLimiter;
pub use rate_limited_sink::RateLimitedAuditSink;
pub use sink::AuditEvent;
pub use sink::AuditSink;
pub use sink::IdTokenFailureKind;
pub use sink::NoopAuditSink;
pub use sink::VerifyErrorKind;
pub use sink::compose_id_token_source_id;
pub use sink::compose_source_id;

Modules§

rate_limit
RateLimiter port + MemoryRateLimiter token-bucket adapter (M49).
rate_limited_sink
RateLimitedAuditSink — composition adapter wrapping any AuditSink with any RateLimiter (refinement #1 from Phase 9 deep-module audit).
sink
AuditSink port + AuditEvent value type + ship-with adapters (NoopAuditSink, MemoryAuditSink).

Structs§

RateLimitKey
Opaque per-source bucket key for a RateLimiter.