haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use super::{Expected, Issuer};
use crate::assistant::{config::Seat, permission::Decision, tools::Identity};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use serde_json::{Value, json};
use tokio::io::{AsyncBufReadExt as _, AsyncReadExt as _, AsyncWriteExt as _, BufReader};

fn token(expires: u64) -> anyhow::Result<String> {
    Ok(format!(
        "e30.{}.c2ln",
        URL_SAFE_NO_PAD.encode(serde_json::to_vec(&json!({"exp":expires}))?)
    ))
}
async fn fixture(
    responses: Vec<(&'static str, u16, Value)>,
    expires: u64,
) -> anyhow::Result<(Issuer, tokio::task::JoinHandle<anyhow::Result<()>>)> {
    use std::os::unix::fs::PermissionsExt as _;
    let directory = tempfile::Builder::new()
        .permissions(std::fs::Permissions::from_mode(0o700))
        .tempdir()?;
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
    let origin = format!("http://{}", listener.local_addr()?);
    let worker = tokio::spawn(async move {
        for (expected, status, body) in responses {
            let mut peer = BufReader::new(listener.accept().await?.0);
            let mut line = String::new();
            peer.read_line(&mut line).await?;
            anyhow::ensure!(
                line.starts_with(expected),
                "watch request order differs: expected {expected}, observed {}",
                line.trim()
            );
            let mut length = 0;
            loop {
                line.clear();
                peer.read_line(&mut line).await?;
                if line == "\r\n" {
                    break;
                }
                if let Some(value) = line.to_ascii_lowercase().strip_prefix("content-length:") {
                    length = value.trim().parse()?;
                }
            }
            let mut received = vec![0; length];
            peer.read_exact(&mut received).await?;
            let bytes = serde_json::to_vec(&body)?;
            let header = format!(
                "HTTP/1.1 {status} Fixture\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
                bytes.len()
            );
            peer.get_mut().write_all(header.as_bytes()).await?;
            peer.get_mut().write_all(&bytes).await?;
            peer.get_mut().shutdown().await?;
        }
        Ok(())
    });
    let seat: Seat = serde_json::from_value(
        json!({"harness":{"id":"codex","home":directory.path(),"program":"/bin/true","args":[]},"door_socket":"/tmp/unused","serve_config":"/tmp/unused","issuer":origin,"store":"fixture"}),
    )?;
    let identity = Identity {
        agent: "agent".into(),
        launch: serde_json::to_string(&json!({"headers":{"lys-agent-pass":"fixture-proof"}}))?,
        pass: token(expires)?,
    };
    Ok((Issuer::new(&seat, &identity)?, worker))
}
fn now() -> anyhow::Result<u64> {
    Ok(std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?
        .as_secs())
}
fn draft(state: &str) -> Value {
    json!({"id":"draft","state":state,"holder":"agent","target":{"kind":"haematite.database","id":"fixture","action":"agent_permission"},"request_digest":"digest","decided_by":"person","decided_at":1})
}
#[tokio::test]
async fn round4_2_all_real_lys_draft_states_are_understood() -> anyhow::Result<()> {
    for state in [
        "waiting",
        "approved",
        "refused",
        "executed",
        "refused_on_execution",
    ] {
        let (issuer, worker) = fixture(
            vec![("GET /api/product-drafts/draft ", 200, draft(state))],
            now()? + 3600,
        )
        .await?;
        let answer = issuer
            .draft_answer(
                "draft",
                &Expected {
                    digest: "digest".into(),
                },
            )
            .await;
        worker.await??;
        let answer = answer?;
        if state == "waiting" {
            assert!(answer.is_none());
            continue;
        }
        let answer = answer.ok_or_else(|| anyhow::anyhow!("terminal state unanswered"))?;
        assert_eq!(answer.person, "person");
        assert!(match state {
            "approved" => matches!(answer.decision, Decision::Approve),
            "executed" => matches!(answer.decision, Decision::Cancelled),
            _ => matches!(answer.decision, Decision::Refuse),
        });
    }
    Ok(())
}
#[tokio::test]
async fn round4_3_near_expiry_watch_pass_is_renewed_before_reading() -> anyhow::Result<()> {
    let expires = now()? + 3600;
    let (issuer, worker) = fixture(
        vec![
            (
                "POST /api/agents/agent/pass ",
                200,
                json!({"pass":token(expires)?,"audience":"haematite","expires_at":expires}),
            ),
            ("GET /api/product-drafts/draft ", 200, draft("waiting")),
        ],
        now()? + 1,
    )
    .await?;
    let answer = issuer
        .draft_answer(
            "draft",
            &Expected {
                digest: "digest".into(),
            },
        )
        .await;
    let served = worker.await?;
    served?;
    assert!(answer?.is_none());
    Ok(())
}
#[tokio::test]
async fn round4_3_watch_refusal_reports_its_http_status() -> anyhow::Result<()> {
    let (issuer, worker) = fixture(
        vec![(
            "GET /api/product-drafts/draft ",
            403,
            json!({"refusal":"fixture"}),
        )],
        now()? + 3600,
    )
    .await?;
    let result = issuer
        .draft_answer(
            "draft",
            &Expected {
                digest: "digest".into(),
            },
        )
        .await;
    worker.await??;
    let error = result
        .err()
        .ok_or_else(|| anyhow::anyhow!("HTTP refusal accepted"))?;
    assert!(error.to_string().contains("HTTP 403"), "{error}");
    Ok(())
}