sim-lib-agent 0.1.2

Agent runtime surfaces for SIM.
Documentation
use sim_kernel::Symbol;
use sim_lib_stream_core::StreamPacket;

use super::{AgentMission, AtelierAction, GuardCapability, GuardDecision, evaluate_guarded_action};

fn mission_with(capability: GuardCapability) -> AgentMission {
    AgentMission::new(
        Symbol::qualified("agent/mission", "guard-test"),
        "sim-agent-net",
    )
    .with_capability(capability)
}

fn assert_refusal(eval: &super::GuardEvaluation) -> &str {
    let GuardDecision::Refused(refusal) = eval.decision() else {
        panic!("expected guard refusal");
    };
    let cassette = eval.cassette().expect("refusal should record a cassette");
    let envelope = &cassette.cassette().envelopes()[0];
    let StreamPacket::Data(packet) = envelope.packet() else {
        panic!("refusal should record a data packet");
    };
    assert_eq!(packet.kind, Symbol::qualified("ide/event", "refusal"));
    refusal.reason()
}

#[test]
fn leased_repo_edit_with_token_is_granted() {
    let mission = mission_with(GuardCapability::EditRepo("sim-agent-net".to_owned()));
    let eval = evaluate_guarded_action(
        &mission,
        AtelierAction::edit_file("sim-agent-net", "crates/sim-lib-agent/src/atelier.rs"),
        Symbol::qualified("atelier/node", "guard"),
        Symbol::qualified("atelier/dev", "granted"),
    )
    .unwrap();

    assert!(eval.decision().is_granted());
    assert!(eval.cassette().is_none());
}

#[test]
fn edit_outside_lease_is_refused() {
    let mission = mission_with(GuardCapability::EditRepo("sim-stream".to_owned()));
    let eval = evaluate_guarded_action(
        &mission,
        AtelierAction::edit_file("sim-stream", "crates/sim-lib-stream-core/src/dev.rs"),
        Symbol::qualified("atelier/node", "guard"),
        Symbol::qualified("atelier/dev", "outside-lease"),
    )
    .unwrap();

    assert!(assert_refusal(&eval).contains("mission lease"));
}

#[test]
fn meta_workspace_edit_is_refused_even_with_repo_token() {
    let mission = mission_with(GuardCapability::EditRepo("sim-agent-net".to_owned()));
    let eval = evaluate_guarded_action(
        &mission,
        AtelierAction::edit_file(
            "sim-agent-net",
            ".meta-workspace/packages/sim-lib-agent/src/lib.rs",
        ),
        Symbol::qualified("atelier/node", "guard"),
        Symbol::qualified("atelier/dev", "meta-workspace"),
    )
    .unwrap();

    assert!(assert_refusal(&eval).contains(".meta-workspace"));
}

#[test]
fn github_remote_action_is_refused_unconditionally() {
    let mission = mission_with(GuardCapability::PlanPin);
    let eval = evaluate_guarded_action(
        &mission,
        AtelierAction::AddGithubRemote {
            remote: "git@example.com:sim/sim-agent-net".to_owned(),
        },
        Symbol::qualified("atelier/node", "guard"),
        Symbol::qualified("atelier/dev", "github"),
    )
    .unwrap();

    assert!(assert_refusal(&eval).contains("GitHub remote"));
}

#[test]
fn publish_flag_and_mirror_actions_are_refused_unconditionally() {
    let mission = mission_with(GuardCapability::PlanPin);
    for (action, expected) in [
        (
            AtelierAction::FlipPublishToGithub {
                repo: "sim-agent-net".to_owned(),
            },
            "publish_to_github",
        ),
        (AtelierAction::PushMirrorRemote, "mirror remote"),
    ] {
        let eval = evaluate_guarded_action(
            &mission,
            action,
            Symbol::qualified("atelier/node", "guard"),
            Symbol::qualified("atelier/dev", "red-line"),
        )
        .unwrap();

        assert!(assert_refusal(&eval).contains(expected));
    }
}

#[test]
fn code_free_repos_reject_rust_edits() {
    let mission = AgentMission::new(
        Symbol::qualified("agent/mission", "guard-test"),
        "repo-control",
    )
    .with_code_free_repo("repo-control")
    .with_capability(GuardCapability::EditRepo("repo-control".to_owned()));
    let eval = evaluate_guarded_action(
        &mission,
        AtelierAction::edit_file("repo-control", "src/lib.rs"),
        Symbol::qualified("atelier/node", "guard"),
        Symbol::qualified("atelier/dev", "code-free"),
    )
    .unwrap();

    assert!(assert_refusal(&eval).contains("Rust source"));
}

#[test]
fn missing_token_records_refusal() {
    let mission = AgentMission::new(
        Symbol::qualified("agent/mission", "guard-test"),
        "sim-agent-net",
    );
    let eval = evaluate_guarded_action(
        &mission,
        AtelierAction::RunValidation {
            repo: "sim-agent-net".to_owned(),
        },
        Symbol::qualified("atelier/node", "guard"),
        Symbol::qualified("atelier/dev", "missing-token"),
    )
    .unwrap();

    assert!(assert_refusal(&eval).contains("missing guard capability"));
}