liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! Exact registered schema comparisons and completion's registration barrier.

use super::check_contract;
use crate::services::lifecycle::site::{
    SiteChange,
    test_support::{Fixture, TestResult, spec},
};

fn registration(output: &str) -> liminal::protocol::WorkerRegistration {
    liminal::protocol::WorkerRegistration {
        namespaces: vec!["echo.request".into()],
        task_queue: "echo.request".into(),
        node: None,
        activity_types: vec!["echo.request".into()],
        identity: "builder".into(),
        activities: vec![liminal::protocol::WorkerActivityDescriptor {
            name: "echo.request".into(),
            input_schema_json: "{\"type\":\"object\"}".into(),
            output_schema_json: output.into(),
        }],
    }
}

#[test]
fn output_contract_mismatch_names_the_subject_and_schema() -> TestResult {
    let fixture = Fixture::new()?;
    fixture.execute("create", SiteChange::CreateSite(spec()))?;
    let record = fixture.lifecycle.record("echo")?;
    check_contract(&record, &registration("{\"type\":\"object\"}"))?;
    let refused = check_contract(&record, &registration("{\"type\":\"string\"}"))
        .err()
        .ok_or("wrong output admitted")?;
    assert_eq!(
        refused.to_string(),
        "subject echo.request: registered output schema differs from site"
    );
    Ok(())
}

#[test]
fn completion_refuses_every_live_trial_connection_before_any_revocation() -> TestResult {
    let fixture = Fixture::new()?;
    fixture.execute("create", SiteChange::CreateSite(spec()))?;
    fixture.execute(
        "bind",
        SiteChange::BindBuilder {
            agent: "builder".into(),
        },
    )?;
    fixture.lifecycle.lock()?.trials.insert(
        "echo".into(),
        std::collections::BTreeMap::from([(
            19,
            super::Registration {
                subject: "echo.request".into(),
                live: true,
            },
        )]),
    );
    let outcome = fixture.execute("complete", SiteChange::Complete)?;
    let lys_pass::drafts::Execution::RefusedOnExecution { reason, .. } = outcome else {
        return Err("live trial completed".into());
    };
    assert_eq!(reason, "trial still registered on connection 19");
    assert_eq!(
        fixture
            .issuer
            .grants
            .lock()
            .map_err(|_| "fixture grants poisoned")?
            .len(),
        2
    );
    fixture.lifecycle.trial_closed(19)?;
    assert!(fixture.lifecycle.trial_connection("echo")?.is_none());
    Ok(())
}

/// An admitted agent pass that allows every subject decision, as a network-wide grant does.
#[derive(Debug)]
struct Agent(&'static str);

impl crate::auth_lys::session::LysSession for Agent {
    fn holder(&self) -> &str {
        self.0
    }
    fn holder_kind(&self) -> Option<&str> {
        Some("agent")
    }
    fn decide(
        &self,
        _subject: &crate::auth_lys::SubjectBinding,
        _action: crate::auth_lys::HotAction,
        _now: u64,
    ) -> Result<crate::auth_lys::GrantReceipt, crate::auth_lys::AccessDecisionError> {
        Ok(crate::auth_lys::GrantReceipt {
            grant: "grant-network".to_owned(),
        })
    }
    fn decide_link(
        &self,
        subject: &crate::auth_lys::SubjectBinding,
        _action: crate::link::LinkAction,
        now: u64,
    ) -> Result<crate::auth_lys::GrantReceipt, crate::auth_lys::AccessDecisionError> {
        self.decide(subject, crate::auth_lys::HotAction::Serve, now)
    }
    fn open_link(
        &self,
        _link: &crate::auth_lys::AccessResource,
        _end: crate::link::LinkAction,
        _now: u64,
    ) -> Result<crate::auth_lys::DeliberateDecision, crate::auth_lys::AccessDecisionError> {
        Ok(crate::auth_lys::DeliberateDecision::Outright(
            crate::auth_lys::GrantReceipt {
                grant: "grant-network".to_owned(),
            },
        ))
    }
    fn decide_live(
        &self,
        link: &crate::auth_lys::AccessResource,
        _action: crate::auth_lys::AccessAction,
        now: u64,
    ) -> Result<crate::auth_lys::DeliberateDecision, crate::auth_lys::AccessDecisionError> {
        self.open_link(link, crate::link::LinkAction::Export, now)
    }
}

#[test]
fn a_bound_builder_serves_only_its_sites_subjects_whatever_its_grant_reaches() -> TestResult {
    let fixture = Fixture::new()?;
    fixture.execute("create", SiteChange::CreateSite(spec()))?;
    fixture.execute(
        "bind",
        SiteChange::BindBuilder {
            agent: "builder".into(),
        },
    )?;
    let mut elsewhere = registration("{\"type\":\"object\"}");
    elsewhere.task_queue = "billing.charge".into();
    elsewhere.namespaces = vec!["billing.charge".into()];
    let refused = fixture
        .lifecycle
        .prepare_trial(7, Some(&Agent("builder")), &elsewhere, None, 30_000)
        .err()
        .ok_or("a bound builder served a subject outside its site")?;
    assert_eq!(
        refused.to_string(),
        "builder of echo serves only its site's subjects"
    );
    // An agent bound to no site is left to its own grants.
    assert!(
        fixture
            .lifecycle
            .prepare_trial(8, Some(&Agent("someone")), &elsewhere, None, 30_000)?
            .is_none()
    );
    Ok(())
}