mod env;
mod receipts;
#[cfg(any(feature = "cli-tool", feature = "mcp"))]
mod sandbox;
mod tool_roots;
#[cfg(any(feature = "cli-tool", feature = "mcp", feature = "agent"))]
pub(crate) use env::process_env_snapshot;
pub(crate) use env::{WorkspaceEnv, merge_inferred_tool_roots, process_env_value};
pub(crate) use receipts::ReceiptServices;
#[cfg(any(feature = "cli-tool", feature = "mcp"))]
pub(crate) use sandbox::SandboxServices;
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use crate::receipts::paths::{RUNX_CWD_ENV, RUNX_PROJECT_DIR_ENV};
use crate::receipts::{RUNX_RECEIPT_SIGN_KID_ENV, RuntimeReceiptSignatureConfig};
use crate::services::{ReceiptServices, WorkspaceEnv};
#[test]
fn skill_env_injects_workspace_and_project_paths() {
let workspace = WorkspaceEnv::new(BTreeMap::new(), PathBuf::from("/tmp/runx-work"));
let env = workspace.skill_env_for_skill(Path::new("/tmp/runx-work/skills/demo"));
assert_eq!(env.get(RUNX_CWD_ENV), Some(&"/tmp/runx-work".to_owned()));
assert_eq!(
env.get(RUNX_PROJECT_DIR_ENV),
Some(&"/tmp/runx-work".to_owned())
);
}
#[test]
fn skill_env_infers_bundled_skill_tools() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let skill_dir = temp.path().join("skills/demo");
let tools_dir = skill_dir.join("tools");
std::fs::create_dir_all(&tools_dir)?;
let workspace = WorkspaceEnv::new(BTreeMap::new(), temp.path().to_path_buf());
let env = workspace.skill_env_for_skill(&skill_dir);
let value = env
.get(crate::services::tool_roots::RUNX_TOOL_ROOTS_ENV)
.ok_or("RUNX_TOOL_ROOTS was not inferred")?;
let paths = std::env::split_paths(value).collect::<Vec<_>>();
assert_eq!(paths.first(), Some(&tools_dir));
Ok(())
}
#[test]
fn receipt_services_resolve_paths_from_workspace_env() {
let env = BTreeMap::from([(RUNX_PROJECT_DIR_ENV.to_owned(), ".runx-custom".to_owned())]);
let workspace = WorkspaceEnv::new(env, PathBuf::from("/tmp/runx-work"));
let receipts = ReceiptServices::from_signature_config(
RuntimeReceiptSignatureConfig::local_development(),
);
let resolved = receipts.resolve_path(&workspace, None, None);
assert_eq!(
resolved.path,
PathBuf::from("/tmp/runx-work/.runx-custom/receipts")
);
}
#[test]
fn receipt_services_local_development_fallback_only_handles_absent_signer_env() {
let receipts = ReceiptServices::from_env_or_local_development(&BTreeMap::new())
.expect("absent signer env should use local-development receipts");
assert!(
receipts
.signature_config()
.production_key_for_kid("any-production-key")
.is_none()
);
let partial_env = BTreeMap::from([(
RUNX_RECEIPT_SIGN_KID_ENV.to_owned(),
"partial-explicit-key".to_owned(),
)]);
let error = ReceiptServices::from_env_or_local_development(&partial_env)
.expect_err("partial signer env must still fail closed");
assert!(error.to_string().contains("set together"));
}
}