liminal-cli 0.3.0

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
//! SDK-017 R4: each reply feeds the next request; a failed step stops the
//! flow by index; a missing pointer stops before sending; the first step
//! may not name a previous reply; too many steps are refused.

use serde_json::json;

use super::{fill, flow};
use crate::network::fake::{Answer, Fake};

fn rail() -> Fake {
    Fake::new(
        json!({}),
        &[
            (
                "embed.request",
                Answer::Reply(json!({ "vector": [1, 2, 3] })),
            ),
            ("store.request", Answer::Echo),
        ],
    )
}

#[test]
fn flow_feeds_each_reply_into_the_next_request() {
    let rail = rail();
    let answer = flow(
        &rail,
        &[
            json!({ "subject": "embed.request", "input": { "text": "a" } }),
            json!({ "subject": "store.request", "input": { "vector": { "$prev": "/vector" } } }),
        ],
    );
    assert!(!answer.is_error, "{:?}", answer.structured);
    assert_eq!(
        rail.pushed(),
        vec![
            ("embed.request".to_owned(), json!({ "text": "a" })),
            ("store.request".to_owned(), json!({ "vector": [1, 2, 3] })),
        ]
    );
    assert_eq!(
        answer.structured["steps"][1]["reply"],
        json!({ "echoed": { "vector": [1, 2, 3] } })
    );
}

#[test]
fn a_failed_step_stops_the_flow_by_index() {
    let rail = rail();
    let answer = flow(
        &rail,
        &[
            json!({ "subject": "embed.request", "input": {} }),
            json!({ "subject": "nobody.request", "input": {} }),
            json!({ "subject": "store.request", "input": {} }),
        ],
    );
    assert!(answer.is_error);
    assert_eq!(
        answer.structured["error"],
        "step 1 stopped the flow: nobody.request: no station serves nobody.request"
    );
    assert_eq!(answer.structured["failed_step"], 1);
    assert_eq!(answer.structured["steps"].as_array().map(Vec::len), Some(1));
    assert_eq!(rail.pushed().len(), 1);
}

#[test]
fn a_missing_pointer_stops_before_sending() {
    let rail = rail();
    let answer = flow(
        &rail,
        &[
            json!({ "subject": "embed.request", "input": {} }),
            json!({ "subject": "store.request", "input": { "v": { "$prev": "/nothing" } } }),
        ],
    );
    assert!(answer.is_error);
    assert_eq!(
        answer.structured["error"],
        "step 1 stopped the flow: $prev \"/nothing\" names nothing in the previous reply"
    );
    assert_eq!(rail.pushed().len(), 1);
}

#[test]
fn flow_refuses_prev_in_the_first_step_and_too_many_steps() {
    let rail = rail();
    let answer = flow(
        &rail,
        &[json!({ "subject": "embed.request", "input": { "$prev": "" } })],
    );
    assert!(answer.is_error);
    assert_eq!(
        answer.structured["error"],
        "step 0 stopped the flow: $prev \"\" in the first step"
    );
    let step = json!({ "subject": "embed.request", "input": {} });
    let answer = flow(
        &rail,
        &[step.clone(), step.clone(), step.clone(), step.clone(), step],
    );
    assert!(answer.is_error);
    assert_eq!(
        answer.structured["error"],
        "the flow has 5 steps, over the limit of 4"
    );
    assert_eq!(rail.pushed().len(), 0);
}

#[test]
fn fill_replaces_pointers_anywhere_and_keeps_the_rest() -> Result<(), String> {
    let prev = json!({ "a": { "b": [10, 20] }, "whole": true });
    let filled = fill(
        &json!({ "x": { "$prev": "/a/b/1" }, "y": [{ "$prev": "" }, 3], "z": "text" }),
        Some(&prev),
    )?;
    assert_eq!(filled, json!({ "x": 20, "y": [prev, 3], "z": "text" }));
    Ok(())
}