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 crate::support::Seat;
use serde_json::{Value, json};
use std::os::unix::fs::{PermissionsExt as _, symlink};

#[test]
fn assist004_r2_harness_gets_its_own_home_and_no_other_harness_home() -> anyhow::Result<()> {
    let inherited = tempfile::tempdir()?;
    for (id, variable, absent) in [
        ("codex", "CODEX_HOME", "CLAUDE_CONFIG_DIR"),
        ("claude-code", "CLAUDE_CONFIG_DIR", "CODEX_HOME"),
    ] {
        let parent = inherited
            .path()
            .to_str()
            .ok_or_else(|| anyhow::anyhow!("fixture path invalid"))?;
        let mut seat = Seat::configured(
            &[],
            &[("CODEX_HOME", parent), ("CLAUDE_CONFIG_DIR", parent)],
            |config| {
                config["harness"]["id"] = json!(id);
                config["harness"]["environment"] = json!(["CODEX_HOME", "CLAUDE_CONFIG_DIR"]);
            },
        )?;
        let initialized = seat.event("initialize")?;
        let actual = initialized["homes"][variable].as_str();
        let expected = seat.directory.path().join("harness-home");
        assert!(
            actual.is_some_and(|home| std::path::Path::new(home) == expected),
            "harness did not receive the seat's home"
        );
        assert!(
            initialized["homes"][absent].is_null(),
            "other harness home was inherited"
        );
        seat.event("session_opened")?;
        seat.close(0)?;
    }
    Ok(())
}

fn refused(configure: impl FnOnce(&mut Value), reason: &str) -> anyhow::Result<()> {
    let mut seat = Seat::configured(&[], &[], configure)?;
    seat.eof();
    let output = seat.finish()?;
    let error = String::from_utf8_lossy(&output.stderr);
    assert_eq!(
        output.status.code(),
        Some(1),
        "invalid harness.home started"
    );
    assert!(
        error.contains("harness.home") && error.contains(reason),
        "home refusal did not name its reason: {error}"
    );
    assert_eq!(
        seat.issuer.count("/api/agents/assistant-fixture/pass")?,
        0,
        "invalid home reached startup"
    );
    Ok(())
}

#[test]
fn assist004_r2_missing_home_is_refused_before_start() -> anyhow::Result<()> {
    refused(
        |config| {
            config["harness"]
                .as_object_mut()
                .map(|harness| harness.remove("home"));
        },
        "home_required",
    )
}

#[test]
fn assist004_r2_relative_home_is_refused_before_start() -> anyhow::Result<()> {
    refused(
        |config| config["harness"]["home"] = json!("relative"),
        "home_must_be_absolute",
    )
}

#[test]
fn assist004_r2_nonexistent_home_is_refused_before_start() -> anyhow::Result<()> {
    let directory = tempfile::tempdir()?;
    refused(
        |config| config["harness"]["home"] = json!(directory.path().join("missing")),
        "home_unreadable",
    )
}

#[test]
fn assist004_r2_shared_home_is_refused_before_start() -> anyhow::Result<()> {
    let directory = tempfile::tempdir()?;
    std::fs::set_permissions(directory.path(), std::fs::Permissions::from_mode(0o755))?;
    refused(
        |config| config["harness"]["home"] = json!(directory.path()),
        "home_not_private",
    )
}

#[test]
fn assist004_r2_symlink_home_is_refused_before_start() -> anyhow::Result<()> {
    let directory = tempfile::tempdir()?;
    let link = directory.path().join("link");
    symlink(directory.path(), &link)?;
    refused(
        |config| config["harness"]["home"] = json!(link),
        "home_unreadable",
    )
}