liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
use super::{
    Arc, DraftRequest, Error, GrantMode, Mode, PreparedDraftKeeper, drafts, request_digest,
};
use crate::agents::records::AgentAct;
use crate::services::lifecycle::{Lifecycle, site::SiteAct};
use drafts::waiting::durable::{ApprovalAgents, Prepared};

/// One verified identity prepares exact words against the owning registries.
/// It carries verified claims only; the bearer is borrowed for the live call.
pub struct PreparedOwner {
    agents: Arc<ApprovalAgents>,
    lifecycle: Arc<Lifecycle>,
    identity: Arc<lys_pass::VerifiedPass>,
    limits: drafts::DraftLimits,
    waiting: drafts::waiting::WaitingLimits,
}
impl PreparedOwner {
    /// Shares the actual target owners and configured admission capacities.
    #[must_use]
    pub const fn new(
        agents: Arc<ApprovalAgents>,
        lifecycle: Arc<Lifecycle>,
        identity: Arc<lys_pass::VerifiedPass>,
        limits: drafts::DraftLimits,
        waiting: drafts::waiting::WaitingLimits,
    ) -> Self {
        Self {
            agents,
            lifecycle,
            identity,
            limits,
            waiting,
        }
    }
    fn exact(&self, holder: &str, request: &DraftRequest, at_ms: u64) -> Result<(), Error> {
        if holder != self.identity.claims().holder.id
            || request_digest(&request.words) != request.request_digest
        {
            return Err(Error::Invalid("prepared identity or digest mismatch"));
        }
        if request.target.kind == "liminal.agent" && request.target.action == "agent_permission" {
            if request.target.id != holder || self.identity.claims().holder.kind != "agent" {
                return Err(Error::Invalid("permission holder mismatch"));
            }
            crate::auth_lys::prepared::validate_permission_words(&request.words)?;
            return drafts::validate_request_bounds(request, self.limits)
                .map_err(drafts::prepared_failure);
        }
        let exact = match request.target.kind.as_str() {
            "liminal.agent" => {
                let act: AgentAct = serde_json::from_str(&request.words)
                    .map_err(|_| Error::Invalid("prepared agent words"))?;
                let change = act.change.clone();
                let action = match &change {
                    crate::agents::records::AgentEvent::Create(_) => "create_agent",
                    crate::agents::records::AgentEvent::Start => "start",
                    crate::agents::records::AgentEvent::Stop => "stop",
                    crate::agents::records::AgentEvent::Retire => "retire",
                    _ => return Err(Error::Invalid("prepared agent action")),
                };
                if act.name != request.target.id || action != request.target.action {
                    return Err(Error::Invalid("prepared agent target"));
                }
                serde_json::to_string(
                    &AgentAct::prepare(act.name, change, &self.identity, at_ms)
                        .map_err(|_| Error::Invalid("prepared agent identity"))?,
                )
            }
            "liminal.network" | "liminal.service" | "liminal.template" => {
                let act: SiteAct = serde_json::from_str(&request.words)
                    .map_err(|_| Error::Invalid("prepared construction words"))?;
                if act.permission_id(self.lifecycle.network_id()) != request.target.id
                    || act.change.kind() != request.target.kind
                    || act.change.action() != request.target.action
                {
                    return Err(Error::Invalid("prepared construction target"));
                }
                serde_json::to_string(
                    &SiteAct::prepare(act.target, act.change, &self.identity)
                        .map_err(|_| Error::Invalid("prepared construction identity"))?,
                )
            }
            _ => return Err(Error::Invalid("prepared target kind")),
        }
        .map_err(|_| Error::Invalid("prepared words encoding"))?;
        if exact != request.words {
            return Err(Error::Invalid(
                "prepared words differ from verified operation",
            ));
        }
        drafts::validate_request_bounds(request, self.limits).map_err(drafts::prepared_failure)
    }
}
impl PreparedDraftKeeper for PreparedOwner {
    fn prepare(&self, holder: &str, request: &DraftRequest, at_ms: u64) -> Result<(), Error> {
        self.exact(holder, request, at_ms)?;
        let mode = match self.identity.evaluate(
            &request.target.kind,
            &request.target.id,
            &request.target.action,
            at_ms / 1000,
        )? {
            lys_pass::Decision::Held { grant, mode } if grant == request.grant => match mode {
                Mode::ByDraft => GrantMode::ByDraft,
                Mode::ByTwo => GrantMode::ByTwo,
                Mode::Outright => return Err(Error::Invalid("prepared approval mode")),
            },
            _ => return Err(Error::Invalid("prepared operation is not held by its pass")),
        };
        let agent = if self.identity.claims().holder.kind == "agent" {
            let record = self
                .agents
                .agents()
                .registry()
                .record(holder)
                .map_err(|_| Error::Invalid("prepared agent is not bound"))?;
            if !record.active()
                || self.identity.claims().holder.responsible.as_deref()
                    != Some(record.owner.as_str())
            {
                return Err(Error::Invalid("prepared agent is retired"));
            }
            Some(record.name)
        } else {
            None
        };
        self.agents
            .journal()
            .update(|snapshot| {
                if let Some(old) = snapshot.prepared.get(&request.operation) {
                    if old.holder != holder
                        || old.request.grant != request.grant
                        || old.request.target != request.target
                        || old.request.words != request.words
                        || old.request.request_digest != request.request_digest
                        || old.mode != mode
                    {
                        return Err(drafts::DraftError::ReceiptMismatch);
                    }
                    return Ok(());
                }
                snapshot.prepared.insert(
                    request.operation.clone(),
                    Prepared {
                        holder: holder.into(),
                        agent,
                        request: request.clone(),
                        at_ms,
                        mode,
                        draft: None,
                    },
                );
                Ok(())
            })
            .map_err(drafts::prepared_failure)
    }
    fn held(
        &self,
        holder: &str,
        id: &str,
        request: &DraftRequest,
        at_ms: u64,
    ) -> Result<(), Error> {
        self.exact(holder, request, at_ms)?;
        if !drafts::valid_metadata(id, self.limits.metadata_bytes) {
            return Err(Error::Invalid("held draft identifier bound"));
        }
        let prepared = self
            .agents
            .journal()
            .read(|snapshot| snapshot.prepared.get(&request.operation).cloned())
            .map_err(drafts::prepared_failure)?
            .ok_or(Error::Invalid("draft preparation missing"))?;
        if prepared.holder != holder
            || prepared.request.grant != request.grant
            || prepared.request.target != request.target
            || prepared.request.words != request.words
            || prepared.request.request_digest != request.request_digest
        {
            return Err(Error::Invalid("held preparation mismatch"));
        }
        if request.target.kind != "liminal.agent" {
            let act: SiteAct = serde_json::from_str(&request.words)
                .map_err(|_| Error::Invalid("held construction words"))?;
            self.lifecycle
                .keep_held_site(
                    id,
                    request,
                    &act,
                    &self.identity,
                    prepared.mode,
                    prepared.at_ms,
                )
                .map_err(|_| Error::CannotAsk("held construction journal failed"))?;
        }
        let state = self
            .agents
            .journal()
            .update(|snapshot| {
                if snapshot.prepared.values().any(|old| {
                    old.draft.as_deref() == Some(id) && old.request.operation != request.operation
                }) {
                    return Err(drafts::DraftError::ReceiptMismatch);
                }
                let old = snapshot
                    .prepared
                    .get_mut(&request.operation)
                    .ok_or(drafts::DraftError::Contract)?;
                if old.draft.as_deref().is_some_and(|bound| bound != id) {
                    return Err(drafts::DraftError::ReceiptMismatch);
                }
                old.draft = Some(id.into());
                if let Some(agent) = &prepared.agent {
                    let waiting = snapshot.waiting.entry(agent.clone()).or_default();
                    waiting
                        .hold(id, &request.words, prepared.at_ms, self.waiting)
                        .map_err(|_| drafts::DraftError::Bound)?;
                    return waiting
                        .snapshot()
                        .map(Some)
                        .map_err(|_| drafts::DraftError::Store);
                }
                Ok(None)
            })
            .map_err(drafts::prepared_failure)?;
        if let (Some(agent), Some(state)) = (&prepared.agent, state) {
            if state["state"] != "waiting" {
                return Ok(());
            }
            let oldest = state["draft"]
                .as_str()
                .ok_or(Error::Invalid("held state draft"))?;
            let words = state["words"]
                .as_str()
                .ok_or(Error::Invalid("held state words"))?;
            crate::agents::state::write_waiting(self.agents.agents(), agent, oldest, words)
                .map_err(|_| Error::CannotAsk("held waiting writer failed"))?;
            self.agents
                .agents()
                .retained()
                .publish(&[(format!("agents.{agent}.state"), state)])
                .map_err(|_| Error::CannotAsk("held waiting metadata failed"))?;
        }
        Ok(())
    }
}