arcature 2026.2.1

Arcature application framework: a high-level Application facade over the certified Arcature subsystems, with the low-level Axum/Tower escape hatch preserved.
Documentation
//! Typed error returned by the request-cache memoization boundary
//! (AP2.1-7, AGENTS.md §18).
//!
//! [`RequestCacheError`] is the only failure type the
//! [`crate::request_cache::RequestCache`] API returns. Variants exist only
//! for failures that can actually happen; the `Resolver` variant is a typed
//! carrier (an opaque `Arc<str>` the caller supplies), never a raw `String`
//! error. See the [`crate::request_cache`] module docs for the concurrency /
//! failure / cancellation semantics.

use std::sync::Arc;

/// The maximum serialized key size, in bytes. A key larger than this is
/// rejected with [`RequestCacheError::OversizedKey`] rather than stored.
/// Bounds the memory an attacker-controlled key can consume in the cache
/// (AGENTS.md §29: no unlimited attacker-controlled work).
pub const MAX_KEY_BYTES: usize = 64 * 1024;

/// The typed error returned by the memoization boundary.
///
/// Variants are added only for failures that can actually happen. The
/// `Resolver` variant carries the resolver's own failure reason as an
/// opaque, caller-supplied string (a typed carrier, not a raw `String`
/// error — §18). The caller converts their resolver's error to
/// [`RequestCacheError`] at the boundary and is responsible for redacting
/// any secret before constructing it.
#[derive(Debug, Clone)]
pub enum RequestCacheError {
    /// The serialized key exceeded [`MAX_KEY_BYTES`]. Carries the limit and
    /// the actual size so the caller can log a useful diagnostic without
    /// the key itself.
    OversizedKey { limit: usize, actual: usize },
    /// The resolver returned an error. Carries the resolver's failure
    /// reason as an opaque `Arc<str>` the caller supplied (the caller
    /// redacts any secret before constructing it). This is a typed
    /// carrier: the variant is named, the message is not a raw `String`
    /// error (§18).
    Resolver(Arc<str>),
}

impl std::fmt::Display for RequestCacheError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::OversizedKey { limit, actual } => {
                write!(f, "request-cache key too large: {actual} > {limit} bytes")
            }
            Self::Resolver(reason) => write!(f, "resolver failed: {reason}"),
        }
    }
}

impl std::error::Error for RequestCacheError {}

impl RequestCacheError {
    /// Construct a [`RequestCacheError::Resolver`] from a value's `Display`.
    ///
    /// The caller asserts the `Display` carries no secret (or has redacted
    /// it). This mirrors `arcature_observe::redact::SafeMessage`: the
    /// boundary trusts the caller's assertion; the cache does not guess.
    #[must_use]
    pub fn from_display<E: std::fmt::Display>(error: &E) -> Self {
        Self::Resolver(Arc::from(error.to_string().as_str()))
    }
}