liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! Persisted construction shapes. Free text has no Debug representation.

use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::num::NonZeroUsize;

/// Required positive operator bounds; reaching a bound refuses before mutation.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteLimits {
    /// Limits active and retired construction records without evicting history.
    pub sites: NonZeroUsize,
    /// Limits subject schemas validated in one admission operation.
    pub subjects: NonZeroUsize,
    /// Limits each schema's compilation input before allocating a validator.
    pub schema_bytes: NonZeroUsize,
    /// Limits purpose, title, note and routing metadata individually.
    pub text_bytes: NonZeroUsize,
    /// Limits steps retained in one complete plan replacement.
    pub steps: NonZeroUsize,
    /// Limits historical transitions; a full history refuses new writes.
    pub transitions: NonZeroUsize,
    /// Limits prepared and completed drafts without evicting execution receipts.
    pub drafts: NonZeroUsize,
    /// Bounds the distinct authenticated people recorded on a draft approval.
    pub approvers: NonZeroUsize,
    /// Limits the whole atomic snapshot, including histories and prepared words.
    pub snapshot_bytes: NonZeroUsize,
    /// Limits one immutable request before parsing or holding a draft.
    pub request_bytes: NonZeroUsize,
}

/// Operator bounds and delegation sources for the construction registry.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConstructionConfig {
    /// Positive construction byte and count limits, all explicitly configured.
    pub limits: SiteLimits,
    /// The two source grants held by this machine; no source is inferred.
    pub grants: super::effects::GrantSources,
}

/// Exactly what the site promises on one served subject.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Contract {
    /// Required input schema, exactly as supplied.
    #[serde(default)]
    pub input_schema_json: String,
    /// Required output schema, exactly as supplied.
    #[serde(default)]
    pub output_schema_json: String,
}

/// Immutable contracts approved before binding any builder.
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteSpec {
    /// One service name segment.
    pub name: String,
    /// One sentence describing its work.
    pub purpose: String,
    /// Exact subject roots it will serve.
    pub subjects: Vec<String>,
    /// One validated pair for every subject.
    #[serde(default)]
    pub contracts: BTreeMap<String, Contract>,
}

/// The exact Lys grant ids, retained for deterministic revocation.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BuilderGrants {
    /// The builder's plan-only grant on this service.
    pub plan: String,
    /// The builder's serve-only grant on this service.
    pub serve: String,
}

/// The connector's authenticated approval, stored without free-text words.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Approval {
    /// Exact Lys draft.
    pub draft: String,
    /// Digest of the immutable approved words.
    pub digest: String,
    /// The actual distinct approving people.
    pub approved_by: Vec<String>,
    /// The server's execution clock, in epoch milliseconds.
    pub executed_at_ms: u64,
    /// The durable execution outcome on this exact target transition.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub receipt: Option<lys_pass::drafts::Execution>,
}
impl From<crate::auth_lys::live::drafts::ApprovalTransition> for Approval {
    fn from(value: crate::auth_lys::live::drafts::ApprovalTransition) -> Self {
        Self {
            draft: value.draft,
            digest: value.digest,
            approved_by: value.approved_by,
            executed_at_ms: value.executed_at_ms,
            receipt: None,
        }
    }
}

/// Deliberate construction events; no payload becomes executable code.
#[derive(Clone, Serialize, Deserialize)]
#[serde(
    tag = "act",
    content = "detail",
    rename_all = "snake_case",
    deny_unknown_fields
)]
pub enum SiteChange {
    /// A contracts-first site, initially without a builder or template.
    CreateSite(SiteSpec),
    /// The named active Lys agent becomes its sole builder.
    BindBuilder { agent: String },
    /// Revokes both recorded builder grants.
    UnbindBuilder,
    /// Records the program exactly approved by the person.
    CreateTemplate(super::super::templates::TemplateProposal),
    /// Updates an existing template without changing read-only configuration.
    UpdateTemplate(super::super::templates::TemplateProposal),
    /// Retires a live template without removing its history.
    RetireTemplate,
    /// Removes the trial serve grant and leaves the site asleep.
    Complete,
}

impl SiteChange {
    /// The schema action used to hold this act.
    #[must_use]
    pub const fn action(&self) -> &'static str {
        match self {
            Self::CreateSite(_) => "create_site",
            Self::BindBuilder { .. } => "bind_builder",
            Self::UnbindBuilder => "unbind_builder",
            Self::CreateTemplate(_) => "create",
            Self::UpdateTemplate(_) => "update",
            Self::RetireTemplate => "retire",
            Self::Complete => "complete",
        }
    }
    /// The resource kind named in the immutable draft.
    #[must_use]
    pub const fn kind(&self) -> &'static str {
        match self {
            Self::CreateSite(_) => "liminal.network",
            Self::CreateTemplate(_) | Self::UpdateTemplate(_) | Self::RetireTemplate => {
                "liminal.template"
            }
            _ => "liminal.service",
        }
    }
}

/// A prepared act whose actor is derived only from an admitted pass.
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteAct {
    /// The placed service id or template name, never a filesystem path.
    pub target: String,
    /// Original verified requester.
    pub holder: String,
    /// Responsible person from the same verified claims.
    pub responsible: String,
    /// The immutable approved event.
    pub change: SiteChange,
}

impl SiteAct {
    /// Binds immutable words to the signed requester before the draft is held.
    ///
    /// # Errors
    /// Unsupported holder kind or missing responsible person.
    pub fn prepare(
        target: String,
        change: SiteChange,
        pass: &lys_pass::VerifiedPass,
    ) -> Result<Self, super::SiteError> {
        let holder = &pass.claims().holder;
        let responsible = match holder.kind.as_str() {
            "person" => holder.id.clone(),
            "agent" => holder
                .responsible
                .clone()
                .ok_or(super::SiteError::Invalid("responsible person"))?,
            _ => return Err(super::SiteError::Invalid("construction holder kind")),
        };
        if responsible.is_empty() {
            return Err(super::SiteError::Invalid("responsible person"));
        }
        Ok(Self {
            target,
            holder: holder.id.clone(),
            responsible,
            change,
        })
    }
}

impl SiteAct {
    /// Creation asks the network before an unplaced site can have inherited rights.
    #[must_use]
    pub fn permission_id<'a>(&'a self, network: &'a str) -> &'a str {
        if matches!(self.change, SiteChange::CreateSite(_)) {
            network
        } else {
            &self.target
        }
    }
}