liminal-server 0.28.1

Standalone server for the liminal messaging bus
Documentation
use super::*;
use crate::services::lifecycle::templates::TemplateSources;

// ---- ACCESS-015 R1: an open exchange's construction and its root policy ----

const POLICY_WORDS: &str = "templates: roots and the record-template policy need \
                            [open.construction] on an open exchange or [auth.lys.construction] \
                            on a gated one";

/// The top-level template policy table over `root`.
fn policy(root: &std::path::Path) -> Result<TemplateSources, Box<dyn std::error::Error>> {
    #[derive(serde::Deserialize)]
    struct Wrap {
        templates: TemplateSources,
    }
    let wrapped: Wrap = toml::from_str(&format!(
        "[templates]\nroots = [\"{}\"]\ncommand_bytes = 1048576\nrecords = 8\narguments = 8\n\
         environment_names = 8\ntext_bytes = 1024\nstop_timeout_ms = 2000\n",
        root.display()
    ))?;
    Ok(wrapped.templates)
}

/// `[open]` with `[open.construction]`'s ten bounds.
fn open_construction() -> Result<crate::config::types::OpenConfig, Box<dyn std::error::Error>> {
    Ok(toml::from_str(
        "[construction]\nsites = 8\nsubjects = 8\nschema_bytes = 16384\ntext_bytes = 1024\n\
         steps = 32\ntransitions = 128\ndrafts = 128\napprovers = 2\nsnapshot_bytes = 1048576\n\
         request_bytes = 65536\n",
    )?)
}

/// An open exchange with a store, `[agents]` and its channel bound.
fn open_exchange() -> Result<ServerConfig, Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    config.auth = None;
    config.cluster = None;
    config.persistence_path = Some(PathBuf::from(env!("CARGO_MANIFEST_DIR")));
    config.agents = Some(crate::agents::config::AgentsConfig::default());
    config.limits.max_channels = Some(64);
    Ok(config)
}

#[test]
fn access015_an_open_exchange_with_construction_and_a_root_policy_validates()
-> Result<(), Box<dyn std::error::Error>> {
    let root = tempfile::tempdir()?;
    let mut config = open_exchange()?;
    config.open = Some(open_construction()?);
    config.templates = policy(root.path())?;
    let message = config_validation_message(validate(&mut config, None));
    assert_eq!(message, "", "an open exchange with construction validates");
    assert!(config.open_construction().is_some());
    Ok(())
}

#[test]
fn access015_a_root_policy_without_construction_is_refused_by_the_exact_words()
-> Result<(), Box<dyn std::error::Error>> {
    let root = tempfile::tempdir()?;
    let mut config = open_exchange()?;
    config.templates = policy(root.path())?;
    let message = config_validation_message(validate(&mut config, None));
    assert!(
        message.lines().any(|line| line == POLICY_WORDS),
        "missing {POLICY_WORDS:?} in {message:?}"
    );
    config.open = Some(open_construction()?);
    config.templates = policy(&root.path().join("absent"))?;
    let message = config_validation_message(validate(&mut config, None));
    assert!(
        message
            .lines()
            .any(|line| line == "[templates] roots must name existing absolute directories"),
        "{message}"
    );
    Ok(())
}

#[test]
fn access015_construction_without_agents_or_a_store_is_refused_by_name()
-> Result<(), Box<dyn std::error::Error>> {
    let mut config = open_exchange()?;
    config.open = Some(open_construction()?);
    config.agents = None;
    config.persistence_path = None;
    let message = config_validation_message(validate(&mut config, None));
    for words in [
        "[open.construction] requires top-level [agents]",
        "[open.construction] requires persistence_path",
    ] {
        assert!(
            message.lines().any(|line| line == words),
            "{words}: {message}"
        );
    }
    Ok(())
}