assay-cli 3.36.0

Policy-as-code gate for MCP agent tool calls, with verifiable evidence and Linux kernel enforcement.
#![allow(deprecated)]

use assert_cmd::Command;
#[cfg(target_os = "linux")]
use std::io::Write;
#[cfg(target_os = "linux")]
use tempfile::{NamedTempFile, TempDir};

fn normalize(s: &[u8]) -> String {
    String::from_utf8_lossy(s).replace("\r\n", "\n")
}

#[cfg(not(target_os = "linux"))]
#[test]
fn contract_monitor_non_linux_exit_40_not_supported() {
    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd.arg("monitor").assert().code(40);

    let stderr = normalize(&assert.get_output().stderr);
    assert!(
        stderr.contains("only supported on Linux"),
        "platform-gate diagnostic line changed unexpectedly: {stderr}"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_missing_ebpf_path_exit_40_not_found() {
    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd
        .arg("monitor")
        .arg("--ebpf")
        .arg("/definitely/missing/assay-ebpf.o")
        .assert()
        .code(40);

    let stderr = normalize(&assert.get_output().stderr);
    assert!(
        stderr.contains("eBPF object not found"),
        "missing-ebpf diagnostic line changed unexpectedly: {stderr}"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_invalid_ebpf_payload_exit_40_load_fail() {
    let mut invalid_ebpf = NamedTempFile::new().expect("temp ebpf");
    invalid_ebpf
        .write_all(b"this-is-not-a-valid-ebpf-object")
        .expect("write invalid ebpf");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd
        .arg("monitor")
        .arg("--ebpf")
        .arg(invalid_ebpf.path())
        .assert()
        .code(40);

    let stderr = normalize(&assert.get_output().stderr);
    assert!(
        stderr.contains("Failed to load eBPF"),
        "invalid-ebpf diagnostic line changed unexpectedly: {stderr}"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_parse_fail_policy_exit_2() {
    let mut invalid_policy = NamedTempFile::new().expect("temp policy");
    invalid_policy
        .write_all(b"version: [invalid")
        .expect("write invalid policy");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd
        .arg("monitor")
        .arg("--policy")
        .arg(invalid_policy.path())
        .assert()
        .code(2);

    let stderr = normalize(&assert.get_output().stderr).to_lowercase();
    assert!(
        stderr.contains("fatal:")
            || stderr.contains("yaml")
            || stderr.contains("expected")
            || stderr.contains("line")
            || stderr.contains("column"),
        "parse-fail diagnostic line changed unexpectedly: {stderr}"
    );
}

#[cfg(target_os = "linux")]
fn ipv6_runtime_policy() -> NamedTempFile {
    let mut policy = NamedTempFile::new().expect("temp policy");
    policy
        .write_all(
            br#"runtime_monitor:
  enabled: true
  provider: "ebpf"
  rules:
    - id: "deny-ipv6"
      type: "net_connect"
      match:
        dest_globs: ["2001:db8::/32"]
      action: "deny"
"#,
        )
        .expect("write IPv6 policy");
    policy
}

#[cfg(target_os = "linux")]
fn ipv4_runtime_policy() -> NamedTempFile {
    let mut policy = NamedTempFile::new().expect("temp policy");
    policy
        .write_all(
            br#"runtime_monitor:
  enabled: true
  provider: "ebpf"
  rules:
    - id: "deny-ipv4"
      type: "net_connect"
      match:
        dest_globs: ["198.51.100.0/24"]
      action: "deny"
"#,
        )
        .expect("write IPv4 policy");
    policy
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_missing_ebpf_retains_failed_health_for_requested_enforcement() {
    let policy = ipv4_runtime_policy();
    let output_dir = TempDir::new().expect("temp output dir");
    let health_path = output_dir.path().join("enforcement-health.json");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    cmd.arg("monitor")
        .arg("--policy")
        .arg(policy.path())
        .arg("--ebpf")
        .arg("/definitely/missing/assay-ebpf.o")
        .arg("--enforcement-health")
        .arg(&health_path)
        .assert()
        .code(40);

    let health: serde_json::Value = serde_json::from_slice(
        &std::fs::read(&health_path).expect("read retained enforcement health"),
    )
    .expect("parse retained enforcement health");
    assert_eq!(
        health["network_enforcement"], "failed",
        "requested enforcement that cannot start must never read as absent"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_missing_ebpf_returns_infra_error_when_failed_health_cannot_be_written() {
    let policy = ipv4_runtime_policy();
    let unwritable_target = TempDir::new().expect("directory cannot be overwritten as a file");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd
        .arg("monitor")
        .arg("--policy")
        .arg(policy.path())
        .arg("--ebpf")
        .arg("/definitely/missing/assay-ebpf.o")
        .arg("--enforcement-health")
        .arg(unwritable_target.path())
        .assert()
        .code(3);

    let stderr = normalize(&assert.get_output().stderr);
    assert!(
        stderr.contains("failed to write enforcement_health artifact"),
        "artifact-write failure diagnostic changed unexpectedly: {stderr}"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_missing_ebpf_retains_absent_health_without_network_enforcement() {
    let output_dir = TempDir::new().expect("temp output dir");
    let health_path = output_dir.path().join("enforcement-health.json");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    cmd.arg("monitor")
        .arg("--ebpf")
        .arg("/definitely/missing/assay-ebpf.o")
        .arg("--enforcement-health")
        .arg(&health_path)
        .assert()
        .code(40);

    let health: serde_json::Value = serde_json::from_slice(
        &std::fs::read(&health_path).expect("read retained enforcement health"),
    )
    .expect("parse retained enforcement health");
    assert_eq!(
        health["network_enforcement"], "absent",
        "a startup failure without requested network enforcement must not claim failure"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_missing_ebpf_returns_infra_error_when_absent_health_cannot_be_written() {
    let unwritable_target = TempDir::new().expect("directory cannot be overwritten as a file");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd
        .arg("monitor")
        .arg("--ebpf")
        .arg("/definitely/missing/assay-ebpf.o")
        .arg("--enforcement-health")
        .arg(unwritable_target.path())
        .assert()
        .code(3);

    let stderr = normalize(&assert.get_output().stderr);
    assert!(
        stderr.contains("failed to write enforcement_health artifact"),
        "artifact-write failure diagnostic changed unexpectedly: {stderr}"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_ipv6_refusal_precedes_ebpf_load_and_writes_failed_health() {
    let policy = ipv6_runtime_policy();
    let output_dir = TempDir::new().expect("temp output dir");
    let health_path = output_dir.path().join("enforcement-health.json");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd
        .arg("monitor")
        .arg("--policy")
        .arg(policy.path())
        .arg("--ebpf")
        .arg("/definitely/missing/assay-ebpf.o")
        .arg("--enforcement-health")
        .arg(&health_path)
        .assert()
        .code(4);

    let stderr = normalize(&assert.get_output().stderr);
    assert!(
        stderr.contains("current enforcement target supports IPv4/TCP only"),
        "IPv6 refusal diagnostic changed unexpectedly: {stderr}"
    );
    assert!(
        health_path.is_file(),
        "fail-closed refusal must retain the requested health artifact"
    );
    let health: serde_json::Value = serde_json::from_slice(
        &std::fs::read(&health_path).expect("read retained enforcement health"),
    )
    .expect("parse retained enforcement health");
    assert_eq!(
        health["network_enforcement"], "failed",
        "unsupported policy must retain failed, never absent"
    );
}

#[cfg(target_os = "linux")]
#[test]
fn contract_monitor_ipv6_refusal_returns_infra_error_when_health_write_fails() {
    let policy = ipv6_runtime_policy();
    let unwritable_target = TempDir::new().expect("directory cannot be overwritten as a file");

    let mut cmd = Command::cargo_bin("assay").expect("assay binary");
    let assert = cmd
        .arg("monitor")
        .arg("--policy")
        .arg(policy.path())
        .arg("--ebpf")
        .arg("/definitely/missing/assay-ebpf.o")
        .arg("--enforcement-health")
        .arg(unwritable_target.path())
        .assert()
        .code(3);

    let stderr = normalize(&assert.get_output().stderr);
    assert!(
        stderr.contains("failed to write enforcement_health artifact"),
        "artifact-write failure diagnostic changed unexpectedly: {stderr}"
    );
}