liminal-server 0.25.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,
    /// 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,
        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)?;
    }
    let answer = live
        .ask_prepared(
            token,
            &PreparedAct {
                holder: &act.holder,
                target: &permission,
                operation,
                words: &words,
                at_ms,
            },
            draft_limits,
        )
        .map_err(|error| SiteError::Pass(error.name().into()))?;
    match answer.decision {
        LiveAnswer::Held { draft, .. } => {
            Ok(serde_json::json!({"state":"held","draft":draft,"words":words}))
        }
        LiveAnswer::Outright { grant } => {
            lifecycle.apply_outright(&act, &grant, operation, &words, at_ms)
        }
        LiveAnswer::Refused { refusal } => Err(SiteError::Pass(refusal)),
    }
}