liminal-server 0.24.5

Standalone server for the liminal messaging bus
Documentation
//! Operator bounds for the single grant-log reader and admission verifier.

use serde::Deserialize;
use std::path::PathBuf;

/// One identified log, read with the app's credential, never a person's session.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GrantStreamConfig {
    /// Stable log identity; comparison prevents crossing a reset domain.
    pub log_identity: String,
    /// Reset epoch, compared together with the identity.
    pub log_epoch: u64,
    /// App credential, read once and zeroized; its bytes are never diagnostic data.
    pub credential_path: PathBuf,
    /// Public grant-event service key as 64 lowercase hex digits, pinned locally.
    pub service_key_path: PathBuf,
    /// Log positions per read; limits replay work independently of history length.
    pub page_positions: u64,
    /// Frames per answer; bounds placement and readiness overhead as well as changes.
    pub max_frames: usize,
    /// Bytes per HTTP answer before JSON decoding; prevents an unbounded allocation.
    pub max_response_bytes: usize,
    /// Bytes per signed event before hex decoding and signature verification.
    pub max_event_bytes: usize,
    /// Revoked ids retained from the complete baseline; never evicted into an allow.
    pub max_revoked_grants: usize,
    /// Credential bytes, checked before reading a file or verifying a pass.
    pub max_credential_bytes: usize,
    /// Published key-set bytes, checked before loading the verification cache.
    pub max_keyset_bytes: usize,
    /// Transport-ended reopens without cursor progress; bounds a failing peer's work.
    pub max_transport_reopens: usize,
    /// Rights in a bound pass; caps local admission work and the dependency request.
    pub max_rights: usize,
    /// Ancestors per right, including its leaf; prevents over-bound verification.
    pub max_chain_grants: usize,
    /// Independent paths per operation; preserves every accepted path without truncation.
    pub max_chains: usize,
    /// Enforce the signed binding and reason-bearing close on every connection.
    pub enforce_revocation: bool,
    /// Exact signed target whose outright right admits a connection under that policy.
    pub admission: lys_pass::Target,
    /// Exact placed resources mapped to subject prefixes, never inferred from ids.
    pub subjects: Vec<GrantSubjectConfig>,
}

/// Mapping only: the verified pass still decides each actual subject and action.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GrantSubjectConfig {
    /// Qualified resource kind in the signed rights.
    pub kind: String,
    /// Exact placed resource id in the signed rights.
    pub resource: String,
    /// Subject prefix whose hot operations depend on those rights.
    pub prefix: String,
}

impl GrantStreamConfig {
    /// Validates bounds before allocating, reading credentials or starting a reader.
    ///
    /// # Errors
    /// Names every invalid operator field; no input value or credential is printed.
    pub fn validate(&self) -> Result<(), crate::ServerError> {
        let mut errors = Vec::new();
        self.collect_errors(&mut errors);
        if errors.is_empty() {
            Ok(())
        } else {
            Err(crate::ServerError::ConfigValidation {
                message: errors.join("\n"),
            })
        }
    }

    pub(crate) fn collect_errors(&self, errors: &mut Vec<String>) {
        use crate::auth_revocation as rev;
        let prefix = "auth.lys.grant_stream";
        for (field, value) in [
            ("max_frames", self.max_frames),
            ("max_response_bytes", self.max_response_bytes),
            ("max_event_bytes", self.max_event_bytes),
            ("max_revoked_grants", self.max_revoked_grants),
            ("max_credential_bytes", self.max_credential_bytes),
            ("max_keyset_bytes", self.max_keyset_bytes),
            ("max_transport_reopens", self.max_transport_reopens),
            ("max_rights", self.max_rights),
            ("max_chain_grants", self.max_chain_grants),
            ("max_chains", self.max_chains),
        ] {
            if value == 0 {
                errors.push(format!("{prefix}.{field}: must be positive"));
            }
        }
        if self.page_positions == 0 {
            errors.push(format!("{prefix}.page_positions: must be positive"));
        }
        if usize::try_from(self.page_positions)
            .ok()
            .and_then(|positions| positions.checked_add(2))
            .is_none_or(|frames| frames > self.max_frames)
        {
            errors.push(format!(
                "{prefix}.max_frames: must cover page_positions and both barriers"
            ));
        }
        if self.max_event_bytes > self.max_response_bytes / 2 {
            errors.push(format!(
                "{prefix}.max_event_bytes: hex event must fit max_response_bytes"
            ));
        }
        for (field, value, ceiling) in [
            ("max_rights", self.max_rights, rev::MAX_BOUND_RIGHTS),
            (
                "max_chain_grants",
                self.max_chain_grants,
                rev::MAX_CHAIN_LEN,
            ),
            ("max_chains", self.max_chains, rev::MAX_CHAINS_PER_RIGHT),
            (
                "max_revoked_grants",
                self.max_revoked_grants,
                rev::MAX_DECISIONS_PER_EVENT,
            ),
        ] {
            if value > ceiling {
                errors.push(format!(
                    "{prefix}.{field}: exceeds authority contract {ceiling}"
                ));
            }
        }
        if self.log_identity.is_empty()
            || self.log_identity.len() > rev::MAX_LOG_IDENTITY_LEN
            || self.log_identity.chars().any(char::is_control)
        {
            errors.push(format!("{prefix}.log_identity: invalid identified log"));
        }
        for (field, path) in [
            ("credential_path", &self.credential_path),
            ("service_key_path", &self.service_key_path),
        ] {
            if path.as_os_str().is_empty() {
                errors.push(format!("{prefix}.{field}: must name a file"));
            }
        }
        if lys_pass::Target::new(
            &self.admission.kind,
            &self.admission.id,
            &self.admission.action,
        )
        .is_err()
        {
            errors.push(format!("{prefix}.admission: an exact target is required"));
        }
        let mut seen = std::collections::BTreeSet::new();
        for mapping in &self.subjects {
            if [&mapping.kind, &mapping.resource, &mapping.prefix]
                .iter()
                .any(|value| value.is_empty() || value.chars().any(char::is_control))
                || mapping.prefix.len() > rev::MAX_RIGHT_PREFIX_LEN
                || mapping.prefix.contains(['*', '>'])
            {
                errors.push(format!("{prefix}.subjects: invalid exact resource mapping"));
            }
            if !seen.insert((&mapping.kind, &mapping.resource, &mapping.prefix)) {
                errors.push(format!("{prefix}.subjects: duplicate mapping"));
            }
        }
        if self.subjects.len() > self.max_rights {
            errors.push(format!("{prefix}.subjects: exceeds max_rights"));
        }
    }
}