liminal-cli 0.3.30

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
//! ACCESS-011 R4 (b, c): with `[auth.lys] decisions = "advise"` a ticket whose
//! pass holds no right on the network's subjects lists, calls and sends, and
//! the exchange counts every advised act; under `enforce` the same ticket is
//! refused by the grant it needs; an expired pass is refused the same way
//! under both, word for word.

use std::error::Error;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};

use serde_json::json;

use super::network::{RunningServer, lys_pass_expired, lys_pass_with};
use super::{CHANNEL, Host, SUBJECT, Ticket, answering_worker};

/// A seat named `name` in `dir`, on `address`, presenting `pass` from a token file.
fn seat_with_pass(
    dir: &Path,
    name: &str,
    address: SocketAddr,
    pass: &str,
) -> Result<PathBuf, Box<dyn Error>> {
    let token = dir.join(format!("{name}.token"));
    std::fs::write(&token, format!("{pass}\n"))?;
    let seat = dir.join(format!("{name}.toml"));
    std::fs::write(
        &seat,
        format!(
            "server = \"{address}\"\nchannel = \"{CHANNEL}\"\ntoken_file = \"{name}.token\"\n\n\
             [limits]\nframe_bytes = 65536\nrecord_bytes = 1048576\nreply_timeout_ms = 5000\n\
             flow_steps = 8\nrequest_bytes = 65536\n"
        ),
    )?;
    Ok(seat)
}

/// `liminal_advised_total` as the exchange's `/metrics` reports it.
fn advised_total(server: &RunningServer) -> Result<u64, Box<dyn Error>> {
    let metrics = server.metrics()?;
    let line = metrics
        .lines()
        .find(|line| line.starts_with("liminal_advised_total"))
        .ok_or_else(|| format!("no liminal_advised_total in:\n{metrics}"))?;
    Ok(line
        .rsplit(' ')
        .next()
        .ok_or("liminal_advised_total carries no value")?
        .parse()?)
}

#[test]
fn access011_advise_lets_a_grantless_ticket_act_and_counts_every_advised_act()
-> Result<(), Box<dyn Error>> {
    let dir = tempfile::tempdir()?;
    let server = RunningServer::start_with(dir.path(), "decisions = \"advise\"")?;
    let (_worker, _inputs) = answering_worker(server.address, 1)?;
    let seat = seat_with_pass(
        dir.path(),
        "grantless",
        server.address,
        &lys_pass_with(&[])?,
    )?;
    let mut host = Host::open(Ticket::spawn(&seat)?, true)?;

    let listed = host.tool("list", &json!({}))?;
    assert_eq!(listed["isError"], false, "{listed}");
    assert!(
        listed["structuredContent"]["stations"]
            .to_string()
            .contains(SUBJECT),
        "{listed}"
    );
    let answer = host.tool(
        "call",
        &json!({ "subject": SUBJECT, "input": { "text": "advised" } }),
    )?;
    assert_eq!(answer["isError"], false, "{answer}");
    assert_eq!(
        answer["structuredContent"]["echoed"]["text"], "advised",
        "{answer}"
    );
    let published = host.tool(
        "send",
        &json!({ "subject": CHANNEL, "text": "{\"note\":\"advised\"}", "key": "advised-1" }),
    )?;
    assert_eq!(published["isError"], false, "{published}");
    let (stderr, code) = host.ticket.close()?;
    assert_eq!(code, 0, "{stderr}");

    let advised = advised_total(&server)?;
    assert!(
        advised >= 2,
        "the subscribe behind list and the call were advised: liminal_advised_total {advised}"
    );
    Ok(())
}

#[test]
fn access011_enforce_refuses_the_same_grantless_ticket_by_the_grant_it_needs()
-> Result<(), Box<dyn Error>> {
    let dir = tempfile::tempdir()?;
    let server = RunningServer::start(dir.path())?;
    let seat = seat_with_pass(
        dir.path(),
        "grantless",
        server.address,
        &lys_pass_with(&[])?,
    )?;
    let mut host = Host::open(Ticket::spawn(&seat)?, true)?;

    let listed = host.tool("list", &json!({}))?;
    assert_eq!(listed["isError"], true, "{listed}");
    let words = listed.to_string();
    assert!(
        words.contains("console.topology") && words.contains("subscribe"),
        "{listed}"
    );
    let (_, code) = host.ticket.close()?;
    assert_ne!(code, i32::MIN, "the ticket exited");
    assert_eq!(advised_total(&server)?, 0, "enforce advises nothing");
    Ok(())
}

#[test]
fn access011_an_expired_pass_is_refused_under_advise_exactly_as_under_enforce()
-> Result<(), Box<dyn Error>> {
    let dir = tempfile::tempdir()?;
    let (advise_dir, enforce_dir) = (dir.path().join("advise"), dir.path().join("enforce"));
    std::fs::create_dir_all(&advise_dir)?;
    std::fs::create_dir_all(&enforce_dir)?;
    let advise = RunningServer::start_with(&advise_dir, "decisions = \"advise\"")?;
    let enforce = RunningServer::start_with(&enforce_dir, "")?;
    let mut refusals = Vec::new();
    for (name, server) in [("advise", &advise), ("enforce", &enforce)] {
        let seat = seat_with_pass(
            dir.path(),
            name,
            server.address,
            &lys_pass_expired("agent-late")?,
        )?;
        let mut host = Host::open(Ticket::spawn(&seat)?, true)?;
        let listed = host.tool("list", &json!({}))?;
        assert_eq!(listed["isError"], true, "{name}: {listed}");
        let words = listed["structuredContent"]["error"]
            .as_str()
            .or_else(|| listed["content"][0]["text"].as_str())
            .ok_or_else(|| format!("{name}: no refusal words in {listed}"))?
            .to_owned();
        assert!(words.contains("pass_expired"), "{name}: {words}");
        refusals.push(words);
        drop(host.ticket.close()?);
    }
    assert_eq!(
        refusals[0], refusals[1],
        "the words do not depend on decisions"
    );
    assert_eq!(
        advised_total(&advise)?,
        0,
        "an unverifiable pass is never advised"
    );
    Ok(())
}