agent-sdk-toolkit 0.1.0-alpha.4

Optional tool-pack helpers layered over agent-sdk-core primitive contracts.
Documentation
use std::{env, process::Command, sync::Arc};

use agent_sdk_core::{
    AgentId, AgentSnapshot, EffectTerminalStatus, IsolationCapability, IsolationCapabilityReport,
    IsolationClass, IsolationLifecycleContext, IsolationLifecycleCoordinator, IsolationMatchStatus,
    IsolationRecord, IsolationRequirementSnapshot, IsolationRuntimeKind, IsolationRuntimeRef,
    IsolationRuntimeRegistry, JournalRecordKind, NetworkIsolationPolicy, PolicyKind, PolicyRef,
    ProviderRouteSnapshot, RuntimePackage, RuntimePackageId, RuntimePackageSidecarId,
    WorkspaceMountMode,
    testing::{FakeIsolationRuntime, FakeJournalStore},
};
use agent_sdk_toolkit::environment::{
    AgentWorkspaceEnvironment, AgentWorkspaceEnvironmentProfile, EgressAllowlist,
    EnvironmentRuntime,
};

#[test]
fn egress_allowlist_validates_canonicalizes_dedups_and_lowers_to_core_policy() {
    let allowlist = EgressAllowlist::new()
        .allow("https://docs.rs")
        .allow("crates.io:443")
        .allow("http://example.test:80")
        .allow("https://docs.rs")
        .allow("http://example.test:80");

    assert_eq!(
        allowlist
            .canonical_entries()
            .expect("allowlist canonicalizes"),
        [
            "https://crates.io:443",
            "https://docs.rs:443",
            "http://example.test:80"
        ]
    );
    assert_eq!(
        allowlist.network_policy().expect("allowlist lowers"),
        NetworkIsolationPolicy::EgressScoped {
            rules: vec![
                "https://crates.io:443".to_string(),
                "https://docs.rs:443".to_string(),
                "http://example.test:80".to_string(),
            ],
        }
    );
}

#[test]
fn egress_allowlist_reports_invalid_scheme_host_and_port_errors() {
    assert_allowlist_error("ftp://docs.rs", "protocol");
    assert_allowlist_error("https://", "host");
    assert_allowlist_error("exa mple.test:443", "whitespace");
    assert_allowlist_error("https://docs.rs:0", "port");
    assert_allowlist_error("docs.rs:65536", "port");
}

#[test]
fn workspace_environment_profile_defaults_to_no_network() {
    let profile = AgentWorkspaceEnvironmentProfile::new("env.agent.workspace")
        .workspace("workspace.primary")
        .build()
        .expect("profile builds");

    assert_eq!(
        profile.environment().environment_id.as_str(),
        "env.agent.workspace"
    );
    assert_eq!(
        profile.environment().spec.filesystem.workspace.mode,
        WorkspaceMountMode::Snapshot
    );
    assert_eq!(
        profile.environment().spec.network,
        NetworkIsolationPolicy::Disabled
    );
}

#[test]
fn workspace_environment_profile_lowers_allowlist_to_environment_and_snapshot() {
    let allowlist = EgressAllowlist::new()
        .allow("https://docs.rs")
        .allow("crates.io:443")
        .allow("http://example.test:80");
    let profile = AgentWorkspaceEnvironmentProfile::new("env.agent.workspace")
        .workspace("workspace.primary")
        .isolation_class(IsolationClass::Container)
        .prefer_runtime("runtime.fake.container")
        .egress_allowlist(allowlist)
        .build()
        .expect("profile builds");
    let snapshot = profile.isolation_snapshot(
        RuntimePackageSidecarId::new("sidecar.environment.profile"),
        policy("policy.environment.redaction"),
        policy("policy.environment.cleanup"),
        policy("policy.environment.child"),
    );

    assert_eq!(
        profile.environment().spec.requirement.minimum_class,
        IsolationClass::Container
    );
    assert_eq!(
        profile.environment().spec.kind,
        agent_sdk_core::ExecutionEnvironmentKind::Container
    );
    assert_eq!(
        profile.environment().spec.network,
        NetworkIsolationPolicy::EgressScoped {
            rules: vec![
                "https://crates.io:443".to_string(),
                "https://docs.rs:443".to_string(),
                "http://example.test:80".to_string(),
            ],
        }
    );
    assert_eq!(
        snapshot,
        IsolationRequirementSnapshot::from_environment(
            RuntimePackageSidecarId::new("sidecar.environment.profile"),
            profile.environment(),
            policy("policy.environment.redaction"),
            policy("policy.environment.cleanup"),
            policy("policy.environment.child"),
        )
    );
}

#[test]
fn environment_runtime_enum_lowers_to_stable_runtime_refs_and_classes() {
    assert_eq!(
        EnvironmentRuntime::LocalContainer.as_str(),
        "runtime:local.container"
    );
    assert_eq!(
        EnvironmentRuntime::LocalContainer.to_string(),
        "runtime:local.container"
    );
    let runtime_ref: IsolationRuntimeRef = EnvironmentRuntime::LocalContainer.into();
    assert_eq!(runtime_ref.as_str(), "runtime:local.container");
    assert_eq!(
        EnvironmentRuntime::LocalContainer.isolation_class(),
        IsolationClass::Container
    );
    assert_eq!(
        serde_json::to_string(&EnvironmentRuntime::LocalContainer).expect("runtime serializes"),
        "\"runtime:local.container\""
    );
}

#[test]
fn workspace_environment_profile_can_use_known_runtime_enum() {
    let profile = AgentWorkspaceEnvironmentProfile::new("env.agent.workspace")
        .workspace("workspace.primary")
        .runtime(EnvironmentRuntime::LocalContainer)
        .build()
        .expect("profile builds");

    assert_eq!(
        profile.environment().spec.requirement.minimum_class,
        IsolationClass::Container
    );
    assert_eq!(
        profile.environment().spec.kind,
        agent_sdk_core::ExecutionEnvironmentKind::Container
    );
    assert_eq!(
        profile.environment().spec.requirement.preferred_adapters,
        vec![IsolationRuntimeRef::new("runtime:local.container")]
    );
}

#[test]
fn workspace_environment_profile_denies_allowlist_when_runtime_lacks_capability() {
    let profile = AgentWorkspaceEnvironmentProfile::new("env.agent.workspace")
        .workspace("workspace.primary")
        .isolation_class(IsolationClass::Container)
        .prefer_runtime("runtime.fake.container")
        .egress_allowlist(EgressAllowlist::new().allow("crates.io:443"))
        .build()
        .expect("profile builds");
    let package = package_for(&profile);
    let runtime = Arc::new(FakeIsolationRuntime::with_report(container_report(
        "runtime.fake.container",
    )));
    let mut registry = IsolationRuntimeRegistry::new();
    registry
        .register(runtime.clone())
        .expect("runtime registers");
    let journal = FakeJournalStore::default();
    let coordinator = IsolationLifecycleCoordinator::new(Arc::new(journal), registry);
    let process = profile
        .environment()
        .process(["/bin/echo", "must-not-start"])
        .build()
        .expect("process builds");

    let outcome = coordinator
        .start_process(
            &package,
            profile.environment().clone(),
            process,
            IsolationLifecycleContext::test(package.fingerprint().expect("fingerprint")),
            None,
        )
        .expect("capability gap returns denied outcome");

    assert_eq!(outcome.status, IsolationMatchStatus::DowngradeDenied);
    assert_eq!(runtime.start_process_call_count(), 0);
}

#[test]
fn package_fingerprint_changes_when_profile_network_policy_changes() {
    let offline_profile = AgentWorkspaceEnvironmentProfile::new("env.agent.workspace")
        .workspace("workspace.primary")
        .isolation_class(IsolationClass::Container)
        .prefer_runtime("runtime.fake.container")
        .build()
        .expect("offline profile builds");
    let networked_profile = AgentWorkspaceEnvironmentProfile::new("env.agent.workspace")
        .workspace("workspace.primary")
        .isolation_class(IsolationClass::Container)
        .prefer_runtime("runtime.fake.container")
        .egress_allowlist(EgressAllowlist::new().allow("crates.io:443"))
        .build()
        .expect("networked profile builds");

    assert_ne!(
        package_for(&offline_profile)
            .fingerprint()
            .expect("offline fingerprint"),
        package_for(&networked_profile)
            .fingerprint()
            .expect("networked fingerprint"),
        "network policy must stay part of the canonical isolation fingerprint"
    );
}

#[test]
fn workspace_environment_profile_starts_process_through_fake_runtime_coordinator() {
    let profile = AgentWorkspaceEnvironmentProfile::new("env.agent.workspace")
        .workspace("workspace.primary")
        .isolation_class(IsolationClass::Container)
        .prefer_runtime("runtime.fake.container")
        .build()
        .expect("profile builds");
    let package = package_for(&profile);
    let runtime = Arc::new(FakeIsolationRuntime::with_report(container_report(
        "runtime.fake.container",
    )));
    let mut registry = IsolationRuntimeRegistry::new();
    registry
        .register(runtime.clone())
        .expect("runtime registers");
    let journal = FakeJournalStore::default();
    let coordinator = IsolationLifecycleCoordinator::new(Arc::new(journal.clone()), registry);
    let process = profile
        .environment()
        .process(["/bin/echo", "hello-from-fake-container"])
        .build()
        .expect("process builds");

    let outcome = coordinator
        .start_process(
            &package,
            profile.environment().clone(),
            process,
            IsolationLifecycleContext::test(package.fingerprint().expect("fingerprint")),
            None,
        )
        .expect("fake runtime starts process");

    assert_eq!(outcome.status, IsolationMatchStatus::Matched);
    assert_eq!(runtime.start_process_call_count(), 1);
    let result_record = match outcome.result_record.as_ref().expect("process result") {
        IsolationRecord::ProcessStartResult(record) => record,
        other => panic!("expected process start result record, got {other:?}"),
    };
    assert_eq!(
        result_record.effect_result.terminal_status,
        EffectTerminalStatus::Completed
    );
    assert_eq!(
        journal.records()[0].record_kind,
        JournalRecordKind::EffectIntent
    );
    assert!(
        outcome
            .io_frames
            .iter()
            .all(|frame| !frame.raw_content_present),
        "fake runtime must preserve redacted process I/O defaults"
    );
}

#[test]
fn local_container_runtime_smoke_is_opt_in() {
    if env::var("AGENT_SDK_TOOLKIT_RUN_LOCAL_CONTAINER_SMOKE").as_deref() != Ok("1") {
        return;
    }
    let explicit_cli = env::var("AGENT_SDK_TOOLKIT_CONTAINER_CLI").ok();
    let cli = explicit_cli.as_deref().unwrap_or("container");
    let version = match Command::new(cli).arg("--version").output() {
        Ok(version) => version,
        Err(error) if explicit_cli.is_some() => {
            panic!("explicit container CLI path {cli:?} should run: {error}");
        }
        Err(_) => return,
    };
    assert!(
        version.status.success(),
        "container CLI should report a usable version when smoke is enabled"
    );

    let profile = AgentWorkspaceEnvironmentProfile::new("env.agent.local_container_smoke")
        .workspace("workspace.primary")
        .isolation_class(IsolationClass::Container)
        .prefer_runtime(EnvironmentRuntime::LocalContainer)
        .build()
        .expect("profile builds");
    assert_eq!(
        profile.environment().spec.requirement.minimum_class,
        IsolationClass::Container
    );
    assert_eq!(
        profile.environment().spec.network,
        NetworkIsolationPolicy::Disabled
    );
}

fn assert_allowlist_error(entry: &str, expected: &str) {
    let error = EgressAllowlist::new()
        .allow(entry)
        .network_policy()
        .expect_err("invalid allowlist entry should fail");
    assert!(
        error.context().message.contains(expected),
        "error for {entry:?} should mention {expected:?}, got: {}",
        error.context().message
    );
}

fn package_for(profile: &AgentWorkspaceEnvironment) -> RuntimePackage {
    RuntimePackage::builder(RuntimePackageId::new("package.environment.profile"))
        .agent(AgentSnapshot {
            agent_id: AgentId::new("agent.environment.profile"),
            name: "environment profile".to_string(),
            default_behavior_refs: Vec::new(),
        })
        .provider_route(ProviderRouteSnapshot::new(
            "provider.fake",
            "model.fake.environment",
        ))
        .isolation_requirement(profile.isolation_snapshot(
            RuntimePackageSidecarId::new("sidecar.environment.profile"),
            policy("policy.environment.redaction"),
            policy("policy.environment.cleanup"),
            policy("policy.environment.child"),
        ))
        .build()
        .expect("package builds")
}

fn container_report(runtime_ref: &str) -> IsolationCapabilityReport {
    let mut report = IsolationCapabilityReport::sandbox(runtime_ref);
    report.adapter_kind = IsolationRuntimeKind::Container;
    report.supported_classes = vec![IsolationClass::Container];
    report.capabilities = report.capabilities.with_all([
        IsolationCapability::Cleanup,
        IsolationCapability::ContentRefIo,
        IsolationCapability::IoRedaction,
        IsolationCapability::NoNetworkGuarantee,
        IsolationCapability::ProcessStats,
        IsolationCapability::ProcessTimeout,
        IsolationCapability::ReadOnlyRoot,
        IsolationCapability::SecretIsolation,
    ]);
    report
}

fn policy(id: &str) -> PolicyRef {
    PolicyRef::with_kind(PolicyKind::RuntimePackage, id)
}