liminal-server 0.27.4

Standalone server for the liminal messaging bus
Documentation
//! Exact preparation joins the admitted pass to the existing Lys draft writer.

use super::super::Lifecycle;
use super::{SiteAct, SiteChange, SiteError};
use crate::auth_lys::live::drafts::DraftLimits;
use crate::auth_lys::live::{HttpLysAsk, PreparedAct};
use crate::auth_lys::lys_pass_adapter::LiveAnswer;

/// Borrowed admitted identity and live approval binding; no Debug body.
#[derive(Clone, Copy)]
pub struct VerifiedContext<'a> {
    /// Target owner's registry.
    pub lifecycle: &'a Lifecycle,
    /// Claims verified by the connection's own adapter.
    pub pass: &'a lys_pass::VerifiedPass,
    /// Borrowed solely for the authenticated Lys boundary.
    pub token: &'a str,
    /// Existing live request owner, with this site's preparation keeper.
    pub live: &'a HttpLysAsk,
    /// Operation time observed by the server.
    pub at_ms: u64,
    /// How a missing grant is decided (ACCESS-011 R2).
    pub decisions: crate::config::types::Decisions,
    /// What this connection has been advised on.
    pub warned: &'a crate::auth_lys::advisory::Warned,
    /// Positive approval bounds supplied by the existing authenticated owner.
    pub limits: DraftLimits,
}

/// Called in the admitted-pass adapter while both token and verified claims
/// are borrowed. No token is copied into the target or retained-state store.
///
/// # Errors
/// Invalid actor, expired pass, outright decision, refusal or durable handoff.
pub fn hold_verified(
    context: VerifiedContext<'_>,
    operation: &str,
    target: &str,
    change: SiteChange,
) -> Result<serde_json::Value, SiteError> {
    let VerifiedContext {
        lifecycle,
        pass,
        token,
        live,
        at_ms,
        decisions,
        warned,
        limits: draft_limits,
    } = context;
    let bindings = lifecycle.site_bindings()?;
    let limits = &bindings.limits;
    if pass.claims().exp <= at_ms / 1000 {
        return Err(SiteError::Pass("pass_expired".into()));
    }
    let act = SiteAct::prepare(target.into(), change, pass)?;
    let words = serde_json::to_string(&act).map_err(|_| SiteError::Store)?;
    if words.len() > limits.request_bytes.get() {
        return Err(SiteError::Bound("request_bytes"));
    }
    let permission = lys_pass::Target::new(
        act.change.kind(),
        act.permission_id(lifecycle.network_id()),
        act.change.action(),
    )
    .map_err(|_| SiteError::Invalid("construction target"))?;
    // A template is its own resource in Lys; it sits under the network, or no grant reaches it.
    if act.change.kind() == "liminal.template" {
        bindings.effects.place("liminal.template", &act.target)?;
    }
    // ACCESS-011 R2: a refused, held or unanswered ask proceeds outright
    // under `decisions = "advise"`, with the receipt `advised`.
    let advised = |lacking: &str| {
        crate::auth_lys::advisory::advised(
            decisions,
            warned,
            crate::auth_lys::advisory::Act {
                holder: &act.holder,
                kind: &permission.kind,
                id: &permission.id,
                action: &permission.action,
                lacking,
            },
        )
    };
    let outright = |grant: &str| lifecycle.apply_outright(&act, grant, operation, &words, at_ms);
    let answer = match live.ask_prepared(
        token,
        &PreparedAct {
            holder: &act.holder,
            target: &permission,
            operation,
            words: &words,
            at_ms,
        },
        draft_limits,
    ) {
        Ok(answer) => answer,
        Err(error) => {
            return advised(error.name()).map_or_else(
                || Err(SiteError::Pass(error.name().into())),
                |receipt| outright(&receipt.grant),
            );
        }
    };
    match answer.decision {
        LiveAnswer::Held { draft, .. } => advised(&format!("held under draft {draft}"))
            .map_or_else(
                || Ok(serde_json::json!({"state":"held","draft":draft,"words":words})),
                |receipt| outright(&receipt.grant),
            ),
        LiveAnswer::Outright { grant } => outright(&grant),
        LiveAnswer::Refused { refusal } => advised(&refusal).map_or_else(
            || Err(SiteError::Pass(refusal.clone())),
            |receipt| outright(&receipt.grant),
        ),
    }
}